Compare commits

..

No commits in common. "master" and "1.6.0" have entirely different histories.

17 changed files with 355 additions and 930 deletions

View file

@ -1,25 +0,0 @@
{
"files": [
"README.md"
],
"imageSize": 100,
"commit": false,
"commitConvention": "angular",
"contributors": [
{
"login": "jannikac",
"name": "jannikac",
"avatar_url": "https://avatars.githubusercontent.com/u/21014142?v=4",
"profile": "https://github.com/jannikac",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,
"skipCi": true,
"repoType": "github",
"repoHost": "https://github.com",
"projectName": "gandi-live-dns-rust",
"projectOwner": "SeriousBug"
}

View file

@ -1,3 +0,0 @@
# assert_eq!(..., true) or false is a lot clearer when testing functions that
# return booleans.
bool_assert_comparison = false

View file

@ -1,4 +0,0 @@
ignore:
# These are tested, but the tests hit external services which is not
# necessarily smart to do in CI, so they get skipped.
- "src/ip_source"

443
Cargo.lock generated
View file

@ -13,9 +13,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.69"
version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
checksum = "7724808837b77f4b4de9d283820f9d98bcf496d5692934b857a2399d31ff22e6"
[[package]]
name = "ascii-canvas"
@ -168,9 +168,9 @@ checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524"
[[package]]
name = "async-trait"
version = "0.1.64"
version = "0.1.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2"
checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3"
dependencies = [
"proc-macro2",
"quote",
@ -179,9 +179,9 @@ dependencies = [
[[package]]
name = "atomic-waker"
version = "1.1.0"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "debc29dde2e69f9e47506b525f639ed42300fc014a3e007832592448fa8e4599"
checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a"
[[package]]
name = "atty"
@ -206,12 +206,6 @@ version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "base64"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
[[package]]
name = "basic-cookies"
version = "0.1.4"
@ -260,15 +254,15 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.12.0"
version = "3.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
[[package]]
name = "bytes"
version = "1.4.0"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
[[package]]
name = "castaway"
@ -278,9 +272,9 @@ checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6"
[[package]]
name = "cc"
version = "1.0.79"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
[[package]]
name = "cfg-if"
@ -290,9 +284,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.1.4"
version = "4.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76"
checksum = "4d63b9e9c07271b9957ad22c173bae2a4d9a81127680962039296abcd2f8251d"
dependencies = [
"bitflags",
"clap_derive",
@ -308,9 +302,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.1.0"
version = "4.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8"
checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014"
dependencies = [
"heck",
"proc-macro-error",
@ -321,18 +315,18 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.3.1"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade"
checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "concurrent-queue"
version = "2.1.0"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e"
checksum = "bd7bef69dc86e3c610e4e7aed41035e2a7ed12e72dd7530f61327a6579a4390b"
dependencies = [
"crossbeam-utils",
]
@ -407,10 +401,10 @@ dependencies = [
]
[[package]]
name = "die-exit"
name = "die-exit-2"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38c7de4151fc657b3ef1c90aebd13b25d089166f1f8c1d0a7950ebdfe6499f49"
checksum = "3e9ac3d64c794ef4965e2efc9f9eb3c815f40201a25fe47ee19c0801ec428b7c"
[[package]]
name = "diff"
@ -461,9 +455,9 @@ dependencies = [
[[package]]
name = "either"
version = "1.8.1"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]]
name = "ena"
@ -476,9 +470,9 @@ dependencies = [
[[package]]
name = "encoding_rs"
version = "0.8.32"
version = "0.8.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394"
checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b"
dependencies = [
"cfg-if",
]
@ -542,9 +536,9 @@ dependencies = [
[[package]]
name = "futures"
version = "0.3.26"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84"
checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0"
dependencies = [
"futures-channel",
"futures-core",
@ -557,9 +551,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.26"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed"
dependencies = [
"futures-core",
"futures-sink",
@ -567,15 +561,15 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.26"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
[[package]]
name = "futures-executor"
version = "0.3.26"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e"
checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2"
dependencies = [
"futures-core",
"futures-task",
@ -584,9 +578,9 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.26"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
[[package]]
name = "futures-lite"
@ -605,9 +599,9 @@ dependencies = [
[[package]]
name = "futures-macro"
version = "0.3.26"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d"
dependencies = [
"proc-macro2",
"quote",
@ -616,15 +610,15 @@ dependencies = [
[[package]]
name = "futures-sink"
version = "0.3.26"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9"
[[package]]
name = "futures-task"
version = "0.3.26"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
[[package]]
name = "futures-timer"
@ -634,9 +628,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
[[package]]
name = "futures-util"
version = "0.3.26"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
dependencies = [
"futures-channel",
"futures-core",
@ -652,22 +646,20 @@ dependencies = [
[[package]]
name = "gandi-live-dns"
version = "1.8.0"
version = "1.5.0"
dependencies = [
"anyhow",
"async-trait",
"clap",
"die-exit",
"die-exit-2",
"directories",
"futures",
"governor",
"httpmock",
"json",
"lazy_static",
"regex",
"reqwest",
"serde",
"thiserror",
"tokio",
"toml",
]
@ -685,9 +677,9 @@ dependencies = [
[[package]]
name = "gloo-timers"
version = "0.2.6"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c"
checksum = "98c4a8d6391675c6b2ee1a6c8d06e8e2d03605c44cec1270675985a4c2a5500b"
dependencies = [
"futures-channel",
"futures-core",
@ -740,9 +732,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "heck"
version = "0.4.1"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "hermit-abi"
@ -762,12 +754,6 @@ dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
[[package]]
name = "http"
version = "0.2.8"
@ -811,7 +797,7 @@ dependencies = [
"assert-json-diff",
"async-object-pool",
"async-trait",
"base64 0.13.1",
"base64",
"basic-cookies",
"crossbeam-utils",
"form_urlencoded",
@ -832,9 +818,9 @@ dependencies = [
[[package]]
name = "hyper"
version = "0.14.24"
version = "0.14.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c"
checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c"
dependencies = [
"bytes",
"futures-channel",
@ -898,30 +884,30 @@ dependencies = [
[[package]]
name = "io-lifetimes"
version = "1.0.5"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3"
checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c"
dependencies = [
"libc",
"windows-sys 0.45.0",
"windows-sys 0.42.0",
]
[[package]]
name = "ipnet"
version = "2.7.1"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146"
checksum = "11b0d96e660696543b251e58030cf9787df56da39dab19ad60eae7353040917e"
[[package]]
name = "is-terminal"
version = "0.4.3"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef"
checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330"
dependencies = [
"hermit-abi 0.3.1",
"hermit-abi 0.2.6",
"io-lifetimes",
"rustix",
"windows-sys 0.45.0",
"windows-sys 0.42.0",
]
[[package]]
@ -968,9 +954,9 @@ checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
[[package]]
name = "js-sys"
version = "0.3.61"
version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
dependencies = [
"wasm-bindgen",
]
@ -1036,9 +1022,9 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760"
[[package]]
name = "libc"
version = "0.2.139"
version = "0.2.138"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
[[package]]
name = "libnghttp2-sys"
@ -1133,15 +1119,6 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c"
[[package]]
name = "nom8"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
dependencies = [
"memchr",
]
[[package]]
name = "nonzero_ext"
version = "0.3.0"
@ -1150,19 +1127,19 @@ checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21"
[[package]]
name = "num_cpus"
version = "1.15.0"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
dependencies = [
"hermit-abi 0.2.6",
"hermit-abi 0.1.19",
"libc",
]
[[package]]
name = "once_cell"
version = "1.17.0"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
[[package]]
name = "openssl-probe"
@ -1172,9 +1149,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.80"
version = "0.9.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7"
checksum = "5454462c0eced1e97f2ec09036abc8da362e66802f66fd20f86854d9d8cbcbc4"
dependencies = [
"autocfg",
"cc",
@ -1207,15 +1184,15 @@ dependencies = [
[[package]]
name = "parking_lot_core"
version = "0.9.7"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-sys 0.45.0",
"windows-sys 0.42.0",
]
[[package]]
@ -1226,9 +1203,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "petgraph"
version = "0.6.3"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4"
checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143"
dependencies = [
"fixedbitset",
"indexmap",
@ -1339,9 +1316,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.51"
version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
checksum = "e9d89e5dba24725ae5678020bf8f1357a9aa7ff10736b551adbcd3f8d17d766f"
dependencies = [
"unicode-ident",
]
@ -1364,9 +1341,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.23"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
checksum = "556d0f47a940e895261e77dc200d5eadfc6ef644c179c6f5edfc105e3a2292c8"
dependencies = [
"proc-macro2",
]
@ -1403,9 +1380,9 @@ dependencies = [
[[package]]
name = "raw-cpuid"
version = "10.6.1"
version = "10.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c307f7aacdbab3f0adee67d52739a1d71112cc068d6fab169ddeb18e48877fad"
checksum = "a6823ea29436221176fe662da99998ad3b4db2c7f31e7b6f5fe43adccd6320bb"
dependencies = [
"bitflags",
]
@ -1432,9 +1409,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.7.1"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
dependencies = [
"aho-corasick",
"memchr",
@ -1449,11 +1426,11 @@ checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "reqwest"
version = "0.11.14"
version = "0.11.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9"
checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c"
dependencies = [
"base64 0.21.0",
"base64",
"bytes",
"encoding_rs",
"futures-core",
@ -1503,23 +1480,23 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.36.8"
version = "0.36.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644"
checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys 0.45.0",
"windows-sys 0.42.0",
]
[[package]]
name = "rustls"
version = "0.20.8"
version = "0.20.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f"
checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c"
dependencies = [
"log",
"ring",
@ -1529,11 +1506,11 @@ dependencies = [
[[package]]
name = "rustls-pemfile"
version = "1.0.2"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b"
checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55"
dependencies = [
"base64 0.21.0",
"base64",
]
[[package]]
@ -1550,11 +1527,12 @@ checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
[[package]]
name = "schannel"
version = "0.1.21"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3"
checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2"
dependencies = [
"windows-sys 0.42.0",
"lazy_static",
"windows-sys 0.36.1",
]
[[package]]
@ -1575,18 +1553,18 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.152"
version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
checksum = "97fed41fc1a24994d044e6db6935e69511a1153b52c15eb42493b26fa87feba0"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.152"
version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
checksum = "255abe9a125a985c05190d687b320c12f9b1f0b99445e608c21ba0782c719ad8"
dependencies = [
"proc-macro2",
"quote",
@ -1595,9 +1573,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.93"
version = "1.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76"
checksum = "8778cc0b528968fe72abec38b5db5a20a70d148116cd9325d2bc5f5180ca3faf"
dependencies = [
"itoa",
"ryu",
@ -1614,15 +1592,6 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_spanned"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
dependencies = [
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
@ -1637,9 +1606,9 @@ dependencies = [
[[package]]
name = "signal-hook"
version = "0.3.15"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9"
checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d"
dependencies = [
"libc",
"signal-hook-registry",
@ -1647,9 +1616,9 @@ dependencies = [
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
dependencies = [
"libc",
]
@ -1729,9 +1698,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.107"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
checksum = "09ee3a69cd2c7e06684677e5629b3878b253af05e4714964204279c6bc02cf0b"
dependencies = [
"proc-macro2",
"quote",
@ -1751,9 +1720,9 @@ dependencies = [
[[package]]
name = "termcolor"
version = "1.2.0"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
@ -1808,15 +1777,15 @@ dependencies = [
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.25.0"
version = "1.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46"
dependencies = [
"autocfg",
"bytes",
@ -1856,9 +1825,9 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.7.7"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2"
checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740"
dependencies = [
"bytes",
"futures-core",
@ -1870,36 +1839,11 @@ dependencies = [
[[package]]
name = "toml"
version = "0.7.2"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7afcae9e3f0fe2c370fd4657108972cbb2fa9db1b9f84849cefd80741b01cb6"
checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.19.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6a7712b49e1775fb9a7b998de6635b299237f48b404dde71704f2e0e7f37e5"
dependencies = [
"indexmap",
"nom8",
"serde",
"serde_spanned",
"toml_datetime",
]
[[package]]
@ -1953,9 +1897,9 @@ dependencies = [
[[package]]
name = "try-lock"
version = "0.2.4"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
[[package]]
name = "unicase"
@ -1968,9 +1912,9 @@ dependencies = [
[[package]]
name = "unicode-bidi"
version = "0.3.10"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58"
checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
[[package]]
name = "unicode-ident"
@ -2068,9 +2012,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.84"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
@ -2078,9 +2022,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.84"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
dependencies = [
"bumpalo",
"log",
@ -2093,9 +2037,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.34"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454"
checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d"
dependencies = [
"cfg-if",
"js-sys",
@ -2105,9 +2049,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.84"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -2115,9 +2059,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.84"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
dependencies = [
"proc-macro2",
"quote",
@ -2128,15 +2072,15 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.84"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
[[package]]
name = "web-sys"
version = "0.3.61"
version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97"
checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f"
dependencies = [
"js-sys",
"wasm-bindgen",
@ -2201,6 +2145,19 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
dependencies = [
"windows_aarch64_msvc 0.36.1",
"windows_i686_gnu 0.36.1",
"windows_i686_msvc 0.36.1",
"windows_x86_64_gnu 0.36.1",
"windows_x86_64_msvc 0.36.1",
]
[[package]]
name = "windows-sys"
version = "0.42.0"
@ -2208,79 +2165,85 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_aarch64_msvc 0.42.0",
"windows_i686_gnu 0.42.0",
"windows_i686_msvc 0.42.0",
"windows_x86_64_gnu 0.42.0",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
"windows_x86_64_msvc 0.42.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.1"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.1"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
[[package]]
name = "windows_i686_gnu"
version = "0.42.1"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
[[package]]
name = "windows_i686_gnu"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
[[package]]
name = "windows_i686_msvc"
version = "0.42.1"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
[[package]]
name = "windows_i686_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.1"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.1"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.1"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
[[package]]
name = "winreg"

View file

@ -1,11 +1,11 @@
[package]
name = "gandi-live-dns"
description = "Automatically updates your IP address in Gandi's Live DNS. Makes it possible to use Gandi as a dynamic DNS system."
version = "1.8.0"
version = "1.6.0"
edition = "2021"
authors = ["Kaan Barmore-Genç <kaan@bgenc.net>"]
license = "MIT"
readme = "README.md"
readme = "Readme.md"
repository = "https://github.com/SeriousBug/gandi-live-dns-rust"
[profile.release]
@ -17,7 +17,7 @@ reqwest = { version = "0.11", default-features = false, features = [
"json",
"rustls-tls",
] }
toml = "0.7"
toml = "0.5"
json = "0.12"
serde = { version = "1.0", features = ["derive"] }
directories = "4.0"
@ -32,14 +32,12 @@ futures = "0.3"
anyhow = "1.0"
governor = "0.5"
async-trait = "0.1"
die-exit = "0.4"
thiserror = "1.0.38"
die-exit-2 = "0.4"
[dev-dependencies]
httpmock = "0.6"
regex = "1.6"
lazy_static = "1.4.0"
[dev-dependencies.die-exit]
[dev-dependencies.die-exit-2]
version = "0.4"
features = ["test"]

View file

@ -1,14 +1,6 @@
## Gandi Live Dns Rust <!-- omit in toc -->
## gandi-live-dns-rust
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?label=contributors)](#contributors) <!-- ALL-CONTRIBUTORS-BADGE:END -->
[![tests](https://img.shields.io/github/actions/workflow/status/SeriousBug/gandi-live-dns-rust/test.yml?label=tests&branch=master)](https://github.com/SeriousBug/gandi-live-dns-rust/actions/workflows/test.yml)
[![Test coverage report](https://img.shields.io/codecov/c/github/SeriousBug/gandi-live-dns-rust)](https://codecov.io/gh/SeriousBug/gandi-live-dns-rust)
[![lint checks](https://img.shields.io/github/actions/workflow/status/SeriousBug/gandi-live-dns-rust/lint.yml?label=lints&branch=master)](https://github.com/SeriousBug/gandi-live-dns-rust/actions/workflows/lint.yml)
[![Releases](https://img.shields.io/github/v/release/SeriousBug/gandi-live-dns-rust?include_prereleases)](https://github.com/SeriousBug/gandi-live-dns-rust/releases)
[![Docker Image Size](https://img.shields.io/docker/image-size/seriousbug/gandi-live-dns-rust)](https://hub.docker.com/r/seriousbug/gandi-live-dns-rust)
[![MIT license](https://img.shields.io/github/license/SeriousBug/gandi-live-dns-rust)](https://github.com/SeriousBug/gandi-live-dns-rust/blob/master/LICENSE.txt)
[![tests](https://img.shields.io/github/actions/workflow/status/SeriousBug/gandi-live-dns-rust/test.yml?label=tests&branch=master)](https://github.com/SeriousBug/gandi-live-dns-rust/actions/workflows/test.yml) [![Test coverage report](https://img.shields.io/codecov/c/github/SeriousBug/gandi-live-dns-rust)](https://codecov.io/gh/SeriousBug/gandi-live-dns-rust) [![lint checks](https://img.shields.io/github/actions/workflow/status/SeriousBug/gandi-live-dns-rust/lint.yml?label=lints&branch=master)](https://github.com/SeriousBug/gandi-live-dns-rust/actions/workflows/lint.yml) [![Releases](https://img.shields.io/github/v/release/SeriousBug/gandi-live-dns-rust?include_prereleases)](https://github.com/SeriousBug/gandi-live-dns-rust/releases) [![Docker Image Size](https://img.shields.io/docker/image-size/seriousbug/gandi-live-dns-rust)](https://hub.docker.com/r/seriousbug/gandi-live-dns-rust) [![MIT license](https://img.shields.io/github/license/SeriousBug/gandi-live-dns-rust)](https://github.com/SeriousBug/gandi-live-dns-rust/blob/master/LICENSE.txt)
A program that can set the IP addresses for configured DNS entries in
[Gandi](https://gandi.net)'s domain configuration. Thanks to Gandi's
@ -21,23 +13,6 @@ program can update both IPv4 and IPv6 addresses for one or more domains and
subdomains. It can be used as a one-shot tool managed with a systemd timer
or cron, or a long-running process that reschedules itself.
## Table of Contents <!-- omit in toc -->
- [Usage](#usage)
- [System packages](#system-packages)
- [Prebuilt binaries](#prebuilt-binaries)
- [With docker](#with-docker)
- [From source](#from-source)
- [Automation](#automation)
- [By running as a background process](#by-running-as-a-background-process)
- [Skipped updates](#skipped-updates)
- [With a Systemd timer](#with-a-systemd-timer)
- [Development](#development)
- [Local builds](#local-builds)
- [Making a release](#making-a-release)
- [Alternatives](#alternatives)
- [Contributors](#contributors)
## Usage
The Gandi Live DNS API is rate limited at 30 requests per minute. This program
@ -69,14 +44,8 @@ Download the latest version from the releases page, extract it from the archive,
### With docker
Container images are available on both Github Packages and Docker Hub.
- [ghcr.io/seriousbug/gandi-live-dns-rust](https://github.com/users/seriousbug/packages/container/package/gandi-live-dns-rust)
- [docker.io/seriousbug/gandi-live-dns-rust](https://hub.docker.com/r/seriousbug/gandi-live-dns-rust)
The container images are built multi-arch, with support for x86_64, arm64,
armv7, and armv6 platforms. Follow the steps below to use them. You can use
`seriousbug/gandi-live-dns-rust` directly which will default to Docker Hub,
otherwise add `ghcr.io` in the examples below to use Github Packages.
Use the [seriousbug/gandi-live-dns-rust](https://hub.docker.com/r/seriousbug/gandi-live-dns-rust) Docker images, which are available for x86_64,
arm64, armv6, and armv7 platforms. Follow the steps below to use these images.
- Create a file `gandi.toml`, then copy and paste the contents of [`example.toml`](https://raw.githubusercontent.com/SeriousBug/gandi-live-dns-rust/master/example.toml)
- Follow the instructions in the example config to get your API key and put it in the config
@ -115,23 +84,15 @@ docker run --rm -it -v $(pwd)/gandi.toml:/gandi.toml:ro seriousbug/gandi-live-dn
Or with a `docker-compose.yml` file, add it in the arguments:
```yml
gandi-live-dns:
image: seriousbug/gandi-live-dns-rust:latest
restart: always
volumes:
- ./gandi.toml:/gandi.toml:ro
# Repeat the update every day
command: --repeat=86400
gandi-live-dns:
image: seriousbug/gandi-live-dns-rust:latest
restart: always
volumes:
- ./gandi.toml:/gandi.toml:ro
# Repeat the update every day
command: --repeat=86400
```
#### Skipped updates
In background process mode, the tool will avoid sending an update to Gandi if
your IP address has not changed since the last update. This only works so long
as the tool continues to run, it will send an update when restarted even if your
IP address has not changed. You can also override this behavior by adding
`always_update = true` to the top of your config file.
### With a Systemd timer
The `Packaging` folder contains a Systemd service and timer, which you can use
@ -167,8 +128,7 @@ Docker with `docker login`. Then follow these steps:
- Create a release on Github
- Make sure to create a tag for the release version on `master`
- Upload the binary archives to the Github release
- Update the AUR version
- Run `cargo publish` to update the crates.io version
- Update the AUR version manually
## Alternatives
@ -176,28 +136,3 @@ Docker with `docker login`. Then follow these steps:
- [ Adam Vigneaux's Bash based updater, with a docker image](https://github.com/AdamVig/gandi-dynamic-dns)
- [Yago Riveiro's Python based updater](https://github.com/yriveiro/giu)
- [ Maxime Le Conte des Floris' Go based updater](https://github.com/mlcdf/dyndns)
## Contributors
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tbody>
<tr>
<td align="center"><a href="https://github.com/jannikac"><img src="https://avatars.githubusercontent.com/u/21014142?v=4?s=100" width="100px;" alt="jannikac"/><br /><sub><b>jannikac</b></sub></a><br /><a href="https://github.com/SeriousBug/gandi-live-dns-rust/commits?author=jannikac" title="Code">💻</a></td>
</tr>
</tbody>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->

View file

@ -19,9 +19,8 @@ ttl = 300
# Ipify is used by default. If you want to change it, uncomment the one you want
# to use.
#
#ip_source = "Ipify" # An open source and public service. https://github.com/rdegges/ipify-api
#ip_source = "Icanhazip" # A free service, currently run by Cloudflare. https://major.io/2021/06/06/a-new-future-for-icanhazip/
#ip_source = "SeeIP" # A free service, run by UNVIO, LLC. https://seeip.org/
# ip_source = "Ipify"
# ip_source = "Icanhazip"
# For every domain or subdomain you want to update, create an entry below.

View file

@ -3,7 +3,9 @@
# Make sure `cross` is installed.
# You'll also need `sed`, a relatively recent version of `tar`, and `7z`.
#
DOCKER="docker"
# This script runs does `sudo docker` to build and push the release to docker.
# If you have rootless docker set up, remove sudo from this variable.
DOCKER="sudo docker"
#
shopt -s extglob
# Trap errors and interrupts
@ -47,9 +49,6 @@ VERSION=$(sed -nr 's/^version *= *"([0-9.]+)"/\1/p' Cargo.toml | head --lines=1)
# Make the builds
for target in "${!TARGETS[@]}"; do
echo Building "${target}"
# Keeping the cached builds seem to be breaking things when going between targets
# This wouldn't be a problem if these were running in a matrix on the CI...
rm -rf target/release/
cross build -j $(($(nproc) / 2)) --release --target "${target}"
if [[ "${target}" =~ .*"windows".* ]]; then
zip -j "gandi-live-dns.${VERSION}.${TARGETS[${target}]}.zip" target/"${target}"/release/gandi-live-dns.exe 1>/dev/null
@ -77,6 +76,4 @@ ${DOCKER} buildx build . \
--file "Dockerfile" \
--tag "seriousbug/gandi-live-dns-rust:latest" \
--tag "seriousbug/gandi-live-dns-rust:${VERSION}" \
--tag "ghcr.io/seriousbug/gandi-live-dns-rust:latest" \
--tag "ghcr.io/seriousbug/gandi-live-dns-rust:${VERSION}" \
--push

View file

@ -1,26 +1,13 @@
use crate::opts;
use directories::ProjectDirs;
use serde::Deserialize;
use std::fs;
use std::path::PathBuf;
use std::{fs, io};
use thiserror::Error;
fn default_types() -> Vec<String> {
DEFAULT_TYPES.iter().map(|v| v.to_string()).collect()
}
#[derive(Error, Debug)]
pub enum ConfigError {
#[error("Failed to read config file: {0} ")]
Io(#[from] io::Error),
#[error("Failed to parse config file: {0}")]
Parse(#[from] toml::de::Error),
#[error("Entry '{0}' has invalid type '{1}'")]
Validation(String, String),
#[error("Can't find config directory")]
ConfigNotFound(),
}
#[derive(Deserialize, Debug)]
pub struct Entry {
pub name: String,
@ -38,7 +25,6 @@ fn default_ttl() -> u32 {
pub enum IPSourceName {
Ipify,
Icanhazip,
SeeIP,
}
impl Default for IPSourceName {
@ -58,8 +44,6 @@ pub struct Config {
pub entry: Vec<Entry>,
#[serde(default = "default_ttl")]
pub ttl: u32,
#[serde(default)]
pub always_update: bool,
}
const DEFAULT_TYPES: &[&str] = &["A"];
@ -78,20 +62,18 @@ impl Config {
}
}
fn load_config_from<P: std::convert::AsRef<std::path::Path>>(
path: P,
) -> Result<Config, ConfigError> {
fn load_config_from<P: std::convert::AsRef<std::path::Path>>(path: P) -> anyhow::Result<Config> {
let contents = fs::read_to_string(path)?;
Ok(toml::from_str(&contents)?)
}
pub fn load_config(opts: &opts::Opts) -> Result<Config, ConfigError> {
pub fn load_config(opts: &opts::Opts) -> anyhow::Result<Config> {
let mut config = match &opts.config {
Some(config_path) => load_config_from(config_path),
None => {
let confpath = ProjectDirs::from("me", "kaangenc", "gandi-dynamic-dns")
.map(|dir| PathBuf::from(dir.config_dir()).join("config.toml"))
.ok_or(ConfigError::ConfigNotFound());
.ok_or_else(|| anyhow::anyhow!("Can't find config directory"));
confpath
.and_then(|path| {
println!("Checking for config: {}", path.to_string_lossy());
@ -120,14 +102,11 @@ pub fn load_config(opts: &opts::Opts) -> Result<Config, ConfigError> {
Ok(config)
}
pub fn validate_config(config: &Config) -> Result<(), ConfigError> {
pub fn validate_config(config: &Config) -> anyhow::Result<()> {
for entry in &config.entry {
for entry_type in Config::types(entry) {
if entry_type != "A" && entry_type != "AAAA" {
return Err(ConfigError::Validation(
entry.name.clone(),
entry_type.to_string(),
));
anyhow::bail!("Entry {} has invalid type {}", entry.name, entry_type);
}
}
}
@ -177,7 +156,6 @@ name = "@"
assert_eq!(conf.entry[1].types, vec!["A".to_string()]);
// default
assert_eq!(conf.ip_source, IPSourceName::Ipify);
assert_eq!(conf.always_update, false);
}
#[test]
@ -192,7 +170,6 @@ fqdn = "example.com"
api_key = "yyy"
ttl = 1200
ip_source = "Icanhazip"
always_update = true
[[entry]]
name = "www"
@ -216,7 +193,6 @@ name = "@"
assert_eq!(conf.entry[0].name, "www");
assert_eq!(conf.entry[1].name, "@");
assert_eq!(conf.ip_source, IPSourceName::Icanhazip);
assert_eq!(conf.always_update, true);
}
#[test]

View file

@ -2,5 +2,5 @@ The IP sources. These are APIs that we can query to get the IP address of the
current service.
The tests under this directory are all marked to be skipped, the tests hit the
actual APIs and can be flaky in CI. Make sure to run the tests manually if you
actual APIs and can be flakey in CI. Make sure to run the tests manually if you
have to modify the code.

View file

@ -1,9 +0,0 @@
use async_trait::async_trait;
use crate::ClientError;
#[async_trait]
pub trait IPSource {
async fn get_ipv4(&self) -> Result<String, ClientError>;
async fn get_ipv6(&self) -> Result<String, ClientError>;
}

View file

@ -1,12 +1,10 @@
use async_trait::async_trait;
use crate::ClientError;
use super::ip_source::IPSource;
use super::common::IPSource;
pub(crate) struct IPSourceIcanhazip {}
pub(crate) struct IPSourceIcanhazip;
async fn get_ip(api_url: &str) -> Result<String, ClientError> {
async fn get_ip(api_url: &str) -> anyhow::Result<String> {
let response = reqwest::get(api_url).await?;
let text = response.text().await?;
Ok(text)
@ -14,14 +12,14 @@ async fn get_ip(api_url: &str) -> Result<String, ClientError> {
#[async_trait]
impl IPSource for IPSourceIcanhazip {
async fn get_ipv4(&self) -> Result<String, ClientError> {
async fn get_ipv4() -> anyhow::Result<String> {
Ok(get_ip("https://ipv4.icanhazip.com")
.await?
// icanazip puts a newline at the end
.trim()
.to_string())
}
async fn get_ipv6(&self) -> Result<String, ClientError> {
async fn get_ipv6() -> anyhow::Result<String> {
Ok(get_ip("https://ipv6.icanhazip.com")
.await?
// icanazip puts a newline at the end
@ -34,15 +32,13 @@ impl IPSource for IPSourceIcanhazip {
mod tests {
use regex::Regex;
use crate::ip_source::common::IPSource;
use super::IPSource;
use super::IPSourceIcanhazip;
#[tokio::test]
#[ignore]
async fn ipv4_test() {
let ipv4 = IPSourceIcanhazip
.get_ipv4()
let ipv4 = IPSourceIcanhazip::get_ipv4()
.await
.expect("Failed to get the IP address");
assert!(Regex::new(r"^\d+[.]\d+[.]\d+[.]\d+$")
@ -53,8 +49,7 @@ mod tests {
#[tokio::test]
#[ignore]
async fn ipv6_test() {
let ipv6 = IPSourceIcanhazip
.get_ipv6()
let ipv6 = IPSourceIcanhazip::get_ipv6()
.await
.expect("Failed to get the IP address");
assert!(Regex::new(r"^([0-9a-fA-F]*:){7}[0-9a-fA-F]*$")

View file

@ -0,0 +1,7 @@
use async_trait::async_trait;
#[async_trait]
pub trait IPSource {
async fn get_ipv4() -> anyhow::Result<String>;
async fn get_ipv6() -> anyhow::Result<String>;
}

View file

@ -1,12 +1,10 @@
use async_trait::async_trait;
use crate::ClientError;
use super::ip_source::IPSource;
use super::common::IPSource;
pub(crate) struct IPSourceIpify {}
pub(crate) struct IPSourceIpify;
async fn get_ip(api_url: &str) -> Result<String, ClientError> {
async fn get_ip(api_url: &str) -> anyhow::Result<String> {
let response = reqwest::get(api_url).await?;
let text = response.text().await?;
Ok(text)
@ -14,10 +12,10 @@ async fn get_ip(api_url: &str) -> Result<String, ClientError> {
#[async_trait]
impl IPSource for IPSourceIpify {
async fn get_ipv4(&self) -> Result<String, ClientError> {
async fn get_ipv4() -> anyhow::Result<String> {
get_ip("https://api.ipify.org").await
}
async fn get_ipv6(&self) -> Result<String, ClientError> {
async fn get_ipv6() -> anyhow::Result<String> {
get_ip("https://api6.ipify.org").await
}
}
@ -32,8 +30,7 @@ mod tests {
#[tokio::test]
#[ignore]
async fn ipv4_test() {
let ipv4 = IPSourceIpify
.get_ipv4()
let ipv4 = IPSourceIpify::get_ipv4()
.await
.expect("Failed to get the IP address");
assert!(Regex::new(r"^\d+[.]\d+[.]\d+[.]\d+$")
@ -44,8 +41,7 @@ mod tests {
#[tokio::test]
#[ignore]
async fn ipv6_test() {
let ipv6 = IPSourceIpify
.get_ipv6()
let ipv6 = IPSourceIpify::get_ipv6()
.await
.expect("Failed to get the IP address");
assert!(Regex::new(r"^([0-9a-fA-F]*:){7}[0-9a-fA-F]*$")

View file

@ -1,4 +1,3 @@
pub(crate) mod common;
pub(crate) mod icanhazip;
pub(crate) mod ip_source;
pub(crate) mod ipify;
pub(crate) mod seeip;

View file

@ -1,55 +0,0 @@
use async_trait::async_trait;
use crate::ClientError;
use super::common::IPSource;
pub(crate) struct IPSourceSeeIP;
async fn get_ip(api_url: &str) -> Result<String, ClientError> {
let response = reqwest::get(api_url).await?;
let text = response.text().await?;
Ok(text)
}
#[async_trait]
impl IPSource for IPSourceSeeIP {
async fn get_ipv4(&self) -> Result<String, ClientError> {
get_ip("https://ip4.seeip.org").await
}
async fn get_ipv6(&self) -> Result<String, ClientError> {
get_ip("https://ip6.seeip.org").await
}
}
#[cfg(test)]
mod tests {
use regex::Regex;
use super::IPSource;
use super::IPSourceSeeIP;
#[tokio::test]
#[ignore]
async fn ipv4_test() {
let ipv4 = IPSourceSeeIP
.get_ipv4()
.await
.expect("Failed to get the IP address");
assert!(Regex::new(r"^\d+[.]\d+[.]\d+[.]\d+$")
.unwrap()
.is_match(ipv4.as_str()))
}
#[tokio::test]
#[ignore]
async fn ipv6_test() {
let ipv6 = IPSourceSeeIP
.get_ipv6()
.await
.expect("Failed to get the IP address");
assert!(Regex::new(r"^([0-9a-fA-F]*:){7}[0-9a-fA-F]*$")
.unwrap()
.is_match(ipv6.as_str()))
}
}

View file

@ -1,63 +1,28 @@
use crate::config::Config;
use crate::gandi::GandiAPI;
use crate::ip_source::{common::IPSource, ipify::IPSourceIpify};
use crate::ip_source::{ip_source::IPSource, ipify::IPSourceIpify};
use clap::Parser;
use config::{ConfigError, IPSourceName};
use config::IPSourceName;
use ip_source::icanhazip::IPSourceIcanhazip;
use ip_source::seeip::IPSourceSeeIP;
use opts::Opts;
use reqwest::header::InvalidHeaderValue;
use reqwest::{header, Client, ClientBuilder, StatusCode};
use serde::{Deserialize, Serialize};
use serde::Serialize;
use std::{num::NonZeroU32, sync::Arc, time::Duration};
use tokio::join;
use tokio::{self, task::JoinHandle, time::sleep};
mod config;
mod gandi;
mod ip_source;
mod opts;
use die_exit::*;
use thiserror::Error;
use die_exit_2::*;
/// 30 requests per minute, see https://api.gandi.net/docs/reference/
const GANDI_RATE_LIMIT: u32 = 30;
/// If we hit the rate limit, wait up to this many seconds before next attempt
const GANDI_DELAY_JITTER: u64 = 20;
#[derive(Error, Debug)]
pub enum ClientError {
#[error("Error occured while reading config: {0}")]
Config(#[from] ConfigError),
#[error("Error while accessing the Gandi API: {0}")]
Api(#[from] ApiError),
#[error("Error while converting the API key to a header: {0}")]
InvalidHeader(#[from] InvalidHeaderValue),
#[error("Error while sending request: {0}")]
Request(#[from] reqwest::Error),
#[error("Error while joining async tasks: {0}")]
TaskJoin(#[from] tokio::task::JoinError),
#[error("Unexpected type in config: {0}")]
BadEntry(String),
#[error("Entry '{0}' includes type A which requires an IPv4 adress but no IPv4 adress could be determined because: {1}")]
Ipv4missing(String, String),
#[error("Entry '{0}' includes type AAAA which requires an IPv6 adress but no IPv6 adress could be determined because: {1}")]
Ipv6missing(String, String),
}
#[derive(Error, Debug)]
pub enum ApiError {
#[error("API returned 403 - Forbidden. Message: {message:?}")]
Forbidden { message: String },
#[error("API returned 403 - Unauthorized. Provided API key is possibly incorrect")]
Unauthorized(),
#[error("API returned {0} - {0}")]
Unknown(StatusCode, String),
}
fn api_client(api_key: &str) -> Result<Client, ClientError> {
fn api_client(api_key: &str) -> anyhow::Result<Client> {
let client_builder = ClientBuilder::new();
let key = format!("Apikey {api_key}");
let key = format!("Apikey {}", api_key);
let mut auth_value = header::HeaderValue::from_str(&key)?;
let mut headers = header::HeaderMap::new();
auth_value.set_sensitive(true);
@ -74,202 +39,77 @@ pub struct APIPayload {
pub rrset_ttl: u32,
}
#[derive(Debug)]
struct ResponseFeedback {
entry_name: String,
entry_type: String,
response: Result<String, ApiError>,
}
async fn run<IP: IPSource>(base_url: &str, conf: &Config) -> anyhow::Result<()> {
config::validate_config(conf).die_with(|error| format!("Invalid config: {}", error));
println!("Finding out the IP address...");
let ipv4_result = IP::get_ipv4().await;
let ipv6_result = IP::get_ipv6().await;
let ipv4 = ipv4_result.as_ref();
let ipv6 = ipv6_result.as_ref();
println!("Found these:");
match ipv4 {
Ok(ip) => println!("\tIPv4: {}", ip),
Err(err) => eprintln!("\tIPv4 failed: {}", err),
}
match ipv6 {
Ok(ip) => println!("\tIPv6: {}", ip),
Err(err) => eprintln!("\tIPv6 failed: {}", err),
}
#[derive(Deserialize)]
// Allowing dead code because this is the API response we get from Gandi.
// We don't necessarily need all the fields, but we get them anyway.
#[allow(dead_code)]
struct ApiResponse {
message: String,
cause: Option<String>,
code: Option<i32>,
object: Option<String>,
}
let client = api_client(&conf.api_key)?;
let mut tasks: Vec<JoinHandle<(StatusCode, String)>> = Vec::new();
println!("Attempting to update DNS entries now");
async fn run(
base_url: &str,
ip_source: &Box<dyn IPSource>,
conf: &Config,
opts: &Opts,
) -> Result<(), ClientError> {
let mut last_ipv4: Option<String> = None;
let mut last_ipv6: Option<String> = None;
let governor = Arc::new(governor::RateLimiter::direct(governor::Quota::per_minute(
NonZeroU32::new(GANDI_RATE_LIMIT).die("Governor rate is 0"),
)));
let retry_jitter =
governor::Jitter::new(Duration::ZERO, Duration::from_secs(GANDI_DELAY_JITTER));
loop {
println!("Finding out the IP address...");
let (ipv4_result, ipv6_result) = join!(ip_source.get_ipv4(), ip_source.get_ipv6());
let ipv4 = ipv4_result.as_ref();
let ipv6 = ipv6_result.as_ref();
println!("Found these:");
match ipv4 {
Ok(ip) => println!("\tIPv4: {ip}"),
Err(err) => eprintln!("\tIPv4 failed: {err}"),
}
match ipv6 {
Ok(ip) => println!("\tIPv6: {ip}"),
Err(err) => eprintln!("\tIPv6 failed: {err}"),
}
let ipv4_same = last_ipv4
.as_ref()
.map(|p| ipv4.map(|q| p == q).unwrap_or(false))
.unwrap_or(false);
let ipv6_same = last_ipv6
.as_ref()
.map(|p| ipv6.map(|q| p == q).unwrap_or(false))
.unwrap_or(false);
if !ipv4_same || !ipv6_same || conf.always_update {
let client = api_client(&conf.api_key)?;
let mut tasks: Vec<JoinHandle<Result<ResponseFeedback, ClientError>>> = Vec::new();
println!("Attempting to update DNS entries now");
let governor = Arc::new(governor::RateLimiter::direct(governor::Quota::per_minute(
NonZeroU32::new(GANDI_RATE_LIMIT).die("Governor rate is 0"),
)));
let retry_jitter =
governor::Jitter::new(Duration::ZERO, Duration::from_secs(GANDI_DELAY_JITTER));
for entry in &conf.entry {
for entry_type in Config::types(entry) {
let fqdn = Config::fqdn(entry, conf).to_string();
let url = GandiAPI {
fqdn: &fqdn,
rrset_name: &entry.name,
rrset_type: entry_type,
base_url,
}
.url();
let ip = match entry_type {
"A" => match ipv4 {
Ok(ref value) => Ok(value),
Err(ref err) => Err(ClientError::Ipv4missing(
entry.name.clone(),
err.to_string(),
)),
},
"AAAA" => match ipv6 {
Ok(ref value) => Ok(value),
Err(ref err) => Err(ClientError::Ipv6missing(
entry.name.clone(),
err.to_string(),
)),
},
&_ => Err(ClientError::BadEntry(entry_type.to_string())),
}?;
let payload = APIPayload {
rrset_values: vec![ip.to_string()],
rrset_ttl: Config::ttl(entry, conf),
};
let req = client.put(url).json(&payload);
let task_governor = governor.clone();
let entry_type = entry_type.to_string();
let entry_name = entry.name.to_string();
let task: JoinHandle<Result<ResponseFeedback, ClientError>> =
tokio::task::spawn(async move {
task_governor.until_ready_with_jitter(retry_jitter).await;
println!("Updating {} record for {}", entry_type, &fqdn);
let resp = req.send().await?;
let response_feedback = match resp.status() {
StatusCode::CREATED => {
let body: ApiResponse = resp.json().await?;
ResponseFeedback {
entry_name,
entry_type,
response: Ok(body.message),
}
}
StatusCode::UNAUTHORIZED => ResponseFeedback {
entry_name,
entry_type,
response: Err(ApiError::Unauthorized()),
},
StatusCode::FORBIDDEN => {
let body: ApiResponse = resp.json().await?;
ResponseFeedback {
entry_name: entry_name.clone(),
entry_type,
response: Err(ApiError::Forbidden {
message: body.message,
}),
}
}
_ => {
let status = resp.status();
let body: ApiResponse = resp.json().await?;
ResponseFeedback {
entry_name,
entry_type,
response: Err(ApiError::Unknown(status, body.message)),
}
}
};
Ok(response_feedback)
});
tasks.push(task);
}
for entry in &conf.entry {
for entry_type in Config::types(entry) {
let fqdn = Config::fqdn(entry, conf).to_string();
let url = GandiAPI {
fqdn: &fqdn,
rrset_name: &entry.name,
rrset_type: entry_type,
base_url,
}
let results = futures::future::try_join_all(tasks).await?;
// Only count successfull requests
println!(
"Updates done for {} entries",
results
.iter()
.filter_map(|item| item.as_ref().ok())
.filter(|item| item.response.is_ok())
.count()
);
for item in &results {
match item {
Ok(value) => println!(
"{}",
match &value.response {
Ok(val) => format!(
"Record '{}' ({}): {}",
value.entry_name, value.entry_type, val
),
Err(err) => format!(
"Record '{}' ({}): {}",
value.entry_name, value.entry_type, err
),
}
.url();
let ip = match entry_type {
"A" => ipv4.die_with(|error| format!("Needed IPv4 for {fqdn}: {error}")),
"AAAA" => ipv6.die_with(|error| format!("Needed IPv6 for {fqdn}: {error}")),
bad_entry_type => die!("Unexpected type in config: {}", bad_entry_type),
};
let payload = APIPayload {
rrset_values: vec![ip.to_string()],
rrset_ttl: Config::ttl(entry, conf),
};
let req = client.put(url).json(&payload);
let task_governor = governor.clone();
let entry_type = entry_type.to_string();
let task = tokio::task::spawn(async move {
task_governor.until_ready_with_jitter(retry_jitter).await;
println!("Updating {} record for {}", entry_type, &fqdn);
match req.send().await {
Ok(response) => (
response.status(),
response
.text()
.await
.unwrap_or_else(|error| error.to_string()),
),
Err(err) => println!("{err}"),
Err(error) => (StatusCode::IM_A_TEAPOT, error.to_string()),
}
}
if results
.iter()
// all tasks finished OK, and all responses were OK as well
.all(|result| result.as_ref().map(|v| v.response.is_ok()).unwrap_or(false))
{
// Only then we update the last seen IP, because we want to
// retry updates in case the last update just happened to fail
last_ipv4 = ipv4.ok().map(|v| v.to_string());
last_ipv6 = ipv6.ok().map(|v| v.to_string());
} else if opts.repeat.is_some() {
println!("Some operations failed. They will be retried during the next repeat.")
}
} else {
println!("IP address has not changed since last update");
});
tasks.push(task);
}
}
if let Some(repeat) = opts.repeat {
// If configured to repeat, do so
sleep(Duration::from_secs(repeat)).await;
continue;
}
// Otherwise this is one-shot, we should exit now
break;
let results = futures::future::try_join_all(tasks).await?;
println!("Updates done for {} entries", results.len());
for (status, body) in results {
println!("{} - {}", status, body);
}
Ok(())
@ -278,45 +118,53 @@ async fn run(
#[tokio::main(flavor = "current_thread")]
async fn main() -> anyhow::Result<()> {
let opts = opts::Opts::parse();
let conf = config::load_config(&opts)?;
let conf = config::load_config(&opts)
.die_with(|error| format!("Failed to read config file: {}", error));
let ip_source: Box<dyn IPSource> = match conf.ip_source {
IPSourceName::Ipify => Box::new(IPSourceIpify),
IPSourceName::Icanhazip => Box::new(IPSourceIcanhazip),
IPSourceName::SeeIP => Box::new(IPSourceSeeIP),
};
config::validate_config(&conf)?;
run("https://api.gandi.net", &ip_source, &conf, &opts).await?;
Ok(())
// run indefinitely if repeat is given
if let Some(delay) = opts.repeat {
loop {
run_dispatch(&conf).await.ok();
sleep(Duration::from_secs(delay)).await
}
}
// otherwise run just once
else {
run_dispatch(&conf).await?;
Ok(())
}
}
async fn run_dispatch(conf: &Config) -> anyhow::Result<()> {
match conf.ip_source {
IPSourceName::Ipify => run::<IPSourceIpify>("https://api.gandi.net", conf).await,
IPSourceName::Icanhazip => run::<IPSourceIcanhazip>("https://api.gandi.net", conf).await,
}
}
#[cfg(test)]
mod tests {
use crate::{config, ip_source::common::IPSource, opts::Opts, run, ClientError};
use std::env::temp_dir;
use crate::{config, ip_source::ip_source::IPSource, opts::Opts, run};
use async_trait::async_trait;
use httpmock::MockServer;
use lazy_static::lazy_static;
use std::{
env::temp_dir,
sync::atomic::{AtomicBool, Ordering::SeqCst},
time::Duration,
};
use tokio::{fs, task::LocalSet, time::sleep};
use tokio::fs;
struct IPSourceMock;
struct IPSourceMock {}
#[async_trait]
impl IPSource for IPSourceMock {
async fn get_ipv4(&self) -> Result<String, ClientError> {
async fn get_ipv4() -> anyhow::Result<String> {
Ok("192.168.0.0".to_string())
}
async fn get_ipv6(&self) -> Result<String, ClientError> {
async fn get_ipv6() -> anyhow::Result<String> {
Ok("fe80:0000:0208:74ff:feda:625c".to_string())
}
}
#[tokio::test]
async fn single_shot() {
async fn create_repo_success_test() {
let mut temp = temp_dir().join("gandi-live-dns-test");
fs::create_dir_all(&temp)
.await
@ -338,8 +186,7 @@ mod tests {
"/v5/livedns/domains/{fqdn}/records/{rname}/{rtype}"
))
.body_contains("192.168.0.0");
then.status(201)
.body("{\"cause\":\"\", \"code\":201, \"message\":\"\", \"object\":\"\"}");
then.status(200);
});
let opts = Opts {
@ -347,202 +194,11 @@ mod tests {
..Opts::default()
};
let conf = config::load_config(&opts).expect("Failed to load config");
let ip_source: Box<dyn IPSource> = Box::new(IPSourceMock);
run(server.base_url().as_str(), &ip_source, &conf, &opts)
run::<IPSourceMock>(server.base_url().as_str(), &conf)
.await
.expect("Failed when running the update");
// Assert
mock.assert();
}
#[test]
fn repeat() {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
LocalSet::new().block_on(&runtime, async {
let mut temp = temp_dir().join("gandi-live-dns-test");
fs::create_dir_all(&temp)
.await
.expect("Failed to create test dir");
temp.push("test.toml");
fs::write(
&temp,
"fqdn = \"example.com\"\napi_key = \"xxx\"\nttl = 300\n[[entry]]\nname =\"@\"\n",
)
.await
.expect("Failed to write test config file");
let fqdn = "example.com";
let rname = "@";
let rtype = "A";
let server = MockServer::start();
let mock = server.mock(|when, then| {
when.method("PUT")
.path(format!(
"/v5/livedns/domains/{fqdn}/records/{rname}/{rtype}"
))
.body_contains("192.168.0.0");
then.status(201)
.body("{\"cause\":\"\", \"code\":201, \"message\":\"\", \"object\":\"\"}");
});
let server_url = server.base_url();
let handle = tokio::task::spawn_local(async move {
let opts = Opts {
config: Some(temp.to_string_lossy().to_string()),
repeat: Some(1),
..Opts::default()
};
let conf = config::load_config(&opts).expect("Failed to load config");
let ip_source: Box<dyn IPSource> = Box::new(IPSourceMock);
run(&server_url, &ip_source, &conf, &opts)
.await
.expect("Failed when running the update");
});
sleep(Duration::from_secs(3)).await;
handle.abort();
// Only should update once because the IP doesn't change
mock.assert();
});
}
#[test]
fn repeat_with_failure() {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
LocalSet::new().block_on(&runtime, async {
let mut temp = temp_dir().join("gandi-live-dns-test");
fs::create_dir_all(&temp)
.await
.expect("Failed to create test dir");
temp.push("test.toml");
fs::write(
&temp,
"fqdn = \"example.com\"\napi_key = \"xxx\"\nttl = 300\n[[entry]]\nname =\"@\"\n",
)
.await
.expect("Failed to write test config file");
let fqdn = "example.com";
let rname = "@";
let rtype = "A";
let server = MockServer::start();
let mock = server.mock(|when, then| {
when.method("PUT")
.path(format!(
"/v5/livedns/domains/{fqdn}/records/{rname}/{rtype}"
))
.body_contains("192.168.0.0")
.matches(|_| {
// Don't match during the first call, but do during the second call
lazy_static! {
static ref FIRST_CALL: AtomicBool = AtomicBool::new(true);
}
if FIRST_CALL.load(SeqCst) {
FIRST_CALL.store(false, SeqCst);
return true;
}
false
});
then.status(500)
.body("{\"cause\":\"\", \"code\":500, \"message\":\"Something went wrong\", \"object\":\"\"}");
});
let mock_fail = server.mock(|when, then| {
when.method("PUT")
.path(format!(
"/v5/livedns/domains/{fqdn}/records/{rname}/{rtype}"
))
.body_contains("192.168.0.0");
then.status(201)
.body("{\"cause\":\"\", \"code\":201, \"message\":\"\", \"object\":\"\"}");
});
let server_url = server.base_url();
let handle = tokio::task::spawn_local(async move {
let opts = Opts {
config: Some(temp.to_string_lossy().to_string()),
repeat: Some(1),
..Opts::default()
};
let conf = config::load_config(&opts).expect("Failed to load config");
let ip_source: Box<dyn IPSource> = Box::new(IPSourceMock);
run(&server_url, &ip_source, &conf, &opts)
.await
.expect("Failed when running the update");
});
sleep(Duration::from_secs(4)).await;
handle.abort();
// The first call failed
mock_fail.assert();
// We then retried since the first call failed. The retry succeeds
// so we don't retry again.
mock.assert();
});
}
#[test]
fn repeat_always_update() {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
LocalSet::new().block_on(&runtime, async {
let mut temp = temp_dir().join("gandi-live-dns-test");
fs::create_dir_all(&temp)
.await
.expect("Failed to create test dir");
temp.push("test.toml");
fs::write(
&temp,
"fqdn = \"example.com\"\nalways_update = true\napi_key = \"xxx\"\nttl = 300\n[[entry]]\nname =\"@\"\n",
)
.await
.expect("Failed to write test config file");
let fqdn = "example.com";
let rname = "@";
let rtype = "A";
let server = MockServer::start();
let mock = server.mock(|when, then| {
when.method("PUT")
.path(format!(
"/v5/livedns/domains/{fqdn}/records/{rname}/{rtype}"
))
.body_contains("192.168.0.0");
then.status(201).body("{\"cause\":\"\", \"code\":201, \"message\":\"\", \"object\":\"\"}");
});
let server_url = server.base_url();
let handle = tokio::task::spawn_local(async move {
let opts = Opts {
config: Some(temp.to_string_lossy().to_string()),
repeat: Some(1),
..Opts::default()
};
let conf = config::load_config(&opts).expect("Failed to load config");
let ip_source: Box<dyn IPSource> = Box::new(IPSourceMock);
run(&server_url, &ip_source, &conf, &opts)
.await
.expect("Failed when running the update");
});
sleep(Duration::from_secs(3)).await;
handle.abort();
// Should update multiple times since always_update
assert!(mock.hits() > 1);
});
}
}