Compare commits
592 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5acd3beff5 | ||
|
|
e10830434b | ||
|
|
c7a1d5dcef | ||
|
|
7b4c342631 | ||
|
|
700dbd2d63 | ||
|
|
f5c9e822a7 | ||
|
|
fb5afb2505 | ||
|
|
2882785b9b | ||
|
|
7d9edf4fa0 | ||
|
|
bc70795769 | ||
|
|
ea92d376c7 | ||
|
|
16e97cd9b2 | ||
|
|
20fe83745f | ||
|
|
961cf4dbb5 | ||
|
|
81d1bbf27b | ||
|
|
0fe6bb3a0b | ||
|
|
847a38fe1c | ||
|
|
1db6a5a4e0 | ||
|
|
6388eec95f | ||
|
|
31ac480737 | ||
|
|
6877b58b02 | ||
|
|
d212045058 | ||
|
|
83f34fb377 | ||
|
|
f1a943491a | ||
|
|
75d9724db6 | ||
|
|
531d63d04f | ||
|
|
84999be6fc | ||
|
|
190ccdad8a | ||
|
|
49054242f8 | ||
|
|
930684ffc2 | ||
|
|
3fac1d2ced | ||
|
|
c85579ce4a | ||
|
|
d0dd73220d | ||
|
|
80f4436d98 | ||
|
|
fecc60cfca | ||
|
|
b3d052db80 | ||
|
|
9cd88a0b47 | ||
|
|
56eb522ea5 | ||
|
|
e6feaddc15 | ||
|
|
70ae93f73a | ||
|
|
c3d6e696a1 | ||
|
|
89f2a1eb82 | ||
|
|
307b050579 | ||
|
|
caddf2a029 | ||
|
|
20ff0192d5 | ||
|
|
7d8cfbaeed | ||
|
|
b07101c97a | ||
|
|
447bfb24a9 | ||
|
|
4fff8f4302 | ||
|
|
04bd12d7dd | ||
|
|
4e33a88163 | ||
|
|
4a38df1b59 | ||
|
|
9ec5c0412e | ||
|
|
01676e12f3 | ||
|
|
78f7d08398 | ||
|
|
11d4d14f4d | ||
|
|
ff91f4873a | ||
|
|
778bf7b516 | ||
|
|
cce24c1223 | ||
|
|
b4fabf687b | ||
|
|
74ae6a9096 | ||
|
|
92129ab069 | ||
|
|
d7f50086c3 | ||
|
|
50f883337a | ||
|
|
5a35f7c9d1 | ||
|
|
1b1a712a65 | ||
|
|
724a20e47e | ||
|
|
64c922a1d8 | ||
|
|
905c783ca9 | ||
|
|
4eac62e6ce | ||
|
|
418409ac47 | ||
|
|
4895bfdc55 | ||
|
|
976902f861 | ||
|
|
b06a1cfec0 | ||
|
|
449fce9d6a | ||
|
|
6b45caadfb | ||
|
|
06b49cb810 | ||
|
|
ae58133a0d | ||
|
|
2b0bfb126e | ||
|
|
97841e3f32 | ||
|
|
2fc2baf6f8 | ||
|
|
06355a71da | ||
|
|
9782c430af | ||
|
|
cafdf14008 | ||
|
|
e7569de5c6 | ||
|
|
2b69fe15ed | ||
|
|
bb1f2d02c5 | ||
|
|
3bd093ceb9 | ||
|
|
05c0eab4a8 | ||
|
|
57745f2953 | ||
|
|
9f5ac14aa1 | ||
|
|
8f2aa58fe8 | ||
|
|
01e23d3b69 | ||
|
|
c2da171c85 | ||
|
|
b44dd16ad8 | ||
|
|
ba00016be8 | ||
|
|
e301f483c6 | ||
|
|
ebf25a8ddf | ||
|
|
56d6644194 | ||
|
|
51401dac81 | ||
|
|
18b07898ea | ||
|
|
223739f4a7 | ||
|
|
02ff6a11f0 | ||
|
|
2bff464f65 | ||
|
|
8fe0fe27ea | ||
|
|
5c00caa296 | ||
|
|
344337a3d5 | ||
|
|
e1287564c3 | ||
|
|
8f61de83bf | ||
|
|
f3810cf241 | ||
|
|
3d385f409e | ||
|
|
19bdd47b3c | ||
|
|
32c728ccf8 | ||
|
|
327d67c9ef | ||
|
|
edd7f82351 | ||
|
|
012d43b950 | ||
|
|
e2298f6f1e | ||
|
|
18350f94a6 | ||
|
|
1a9d5df5cb | ||
|
|
8ef1eb8c2a | ||
|
|
7c2018acbc | ||
|
|
2079f6a3eb | ||
|
|
d9419446bf | ||
|
|
c0cbccb63c | ||
|
|
5444dda08a | ||
|
|
224617aae6 | ||
|
|
c941c8a100 | ||
|
|
07a25cd094 | ||
|
|
54d98c10fe | ||
|
|
4a897c40b5 | ||
|
|
9af45d3cab | ||
|
|
9c60b399a8 | ||
|
|
01dbf8e50a | ||
|
|
58d0302c74 | ||
|
|
90ea6a13de | ||
|
|
4f80a7979f | ||
|
|
3812101c25 | ||
|
|
8c79c8b32d | ||
|
|
0afc437252 | ||
|
|
6ae53fea6e | ||
|
|
d925ccf45a | ||
|
|
24243a4b4d | ||
|
|
ef9ebc29b7 | ||
|
|
bd4e0f2b28 | ||
|
|
e952c9f85a | ||
|
|
df67e9a0bd | ||
|
|
7e47ed0879 | ||
|
|
1952ec1527 | ||
|
|
9ca5205d6a | ||
|
|
8a8da9d162 | ||
|
|
6440c71492 | ||
|
|
0b66b2debb | ||
|
|
351c142b91 | ||
|
|
8ce6aed66f | ||
|
|
778d18d816 | ||
|
|
2ea436bbbc | ||
|
|
b5a820b6f4 | ||
|
|
37f5043493 | ||
|
|
7638fb75a3 | ||
|
|
fdad6d3b7b | ||
|
|
074e300974 | ||
|
|
523e068195 | ||
|
|
9a7c617311 | ||
|
|
c214899b75 | ||
|
|
e31a8c8064 | ||
|
|
31cc27df1b | ||
|
|
d60051b5cb | ||
|
|
6770ae2a99 | ||
|
|
84738809ef | ||
|
|
43a08f7eef | ||
|
|
89aeec5f97 | ||
|
|
00ee5415a5 | ||
|
|
b5bd79b61e | ||
|
|
7080c236eb | ||
|
|
bb073eb51e | ||
|
|
7372d49aca | ||
|
|
1d771d4a0a | ||
|
|
dcffdbd98e | ||
|
|
abcc2e4125 | ||
|
|
e453381382 | ||
|
|
9a9913998a | ||
|
|
a3cdb66fe6 | ||
|
|
39aa84e2fd | ||
|
|
1ec2270eb5 | ||
|
|
5d1ee9b538 | ||
|
|
03d3cf57e9 | ||
|
|
2a5465ddb0 | ||
|
|
84efc786d4 | ||
|
|
12e5fb6f7f | ||
|
|
5c66900fdb | ||
|
|
2710ead089 | ||
|
|
839aa420e0 | ||
|
|
4ee42d4092 | ||
|
|
b2950f0718 | ||
|
|
7b1793476d | ||
|
|
f8750ef231 | ||
|
|
cc9283530f | ||
|
|
5a61e56766 | ||
|
|
8f71d514ee | ||
|
|
1b122b228f | ||
|
|
e88c0cdd38 | ||
|
|
3ceb188b81 | ||
|
|
0bc52ecb3c | ||
|
|
36d6c37df3 | ||
|
|
8fd793fdab | ||
|
|
be1909aedb | ||
|
|
e83b4d84ec | ||
|
|
b39b3f38fe | ||
|
|
d50765925b | ||
|
|
e0f8971155 | ||
|
|
f1d53f5610 | ||
|
|
4b4e6b1c72 | ||
|
|
095af716c9 | ||
|
|
e4e9f050df | ||
|
|
638b9b9428 | ||
|
|
0d4d18c595 | ||
|
|
33350c168d | ||
|
|
1bbb882225 | ||
|
|
6e86e8e5c5 | ||
|
|
7cb4c8793a | ||
|
|
4d2c636d9b | ||
|
|
9895d6de43 | ||
|
|
f62b6f94c5 | ||
|
|
e00d9a6c59 | ||
|
|
d2e573bbed | ||
|
|
8c0b6ee70d | ||
|
|
b9c7b1f559 | ||
|
|
7b5b876b0c | ||
|
|
848fb50132 | ||
|
|
6206402cae | ||
|
|
b24dc91ef2 | ||
|
|
d2d58f9058 | ||
|
|
e6fb75f715 | ||
|
|
3b31049636 | ||
|
|
5db091d800 | ||
|
|
8278ecb6a2 | ||
|
|
084eeba24a | ||
|
|
0134840300 | ||
|
|
05335aaf3f | ||
|
|
fb38741036 | ||
|
|
5a07feaffd | ||
|
|
e555c287bf | ||
|
|
ad52f32a3b | ||
|
|
665b27cfbe | ||
|
|
871d558ffd | ||
|
|
881e00e7e3 | ||
|
|
29963b9d75 | ||
|
|
bea8ec2d98 | ||
|
|
851f5fad58 | ||
|
|
1125837420 | ||
|
|
34f9a13da3 | ||
|
|
1432ab4b52 | ||
|
|
8f3a8e3572 | ||
|
|
87d5903cfc | ||
|
|
a50639af23 | ||
|
|
c4b1a70dac | ||
|
|
94677c7a39 | ||
|
|
cd2295ef05 | ||
|
|
944bb42074 | ||
|
|
1350364804 | ||
|
|
3625395d32 | ||
|
|
c32ed90a2e | ||
|
|
9af8c00bca | ||
|
|
0af476ac8e | ||
|
|
3b58e8ce3c | ||
|
|
1fffb80066 | ||
|
|
5f7e6e69e3 | ||
|
|
14e84b23d1 | ||
|
|
562576b8d4 | ||
|
|
532962527f | ||
|
|
99d1914542 | ||
|
|
6fa534dac1 | ||
|
|
3b6da33535 | ||
|
|
ded4a22a67 | ||
|
|
ab0589916f | ||
|
|
3226690d70 | ||
|
|
4d235491f2 | ||
|
|
96b7890404 | ||
|
|
760cc01217 | ||
|
|
c394a05fc1 | ||
|
|
11ab4483e0 | ||
|
|
65747a7f28 | ||
|
|
ca9ddc4222 | ||
|
|
6a2237b513 | ||
|
|
4afccfad8d | ||
|
|
9a32a1d638 | ||
|
|
e091914a06 | ||
|
|
7b584668ac | ||
|
|
30019327da | ||
|
|
fc8eeeaf0a | ||
|
|
11201881c0 | ||
|
|
498dcb8bcb | ||
|
|
5564239bea | ||
|
|
5319c5f53f | ||
|
|
49b9e1ce1f | ||
|
|
82a61aef09 | ||
|
|
3c58a8f091 | ||
|
|
4a62b80e75 | ||
|
|
52c1687d83 | ||
|
|
238a0f46e0 | ||
|
|
bbf5d2d178 | ||
|
|
1c4ea24da1 | ||
|
|
f277460b9d | ||
|
|
15c1643d34 | ||
|
|
e7f0a05e1a | ||
|
|
8506b5d5a5 | ||
|
|
0d629127a9 | ||
|
|
d6d8d2148d | ||
|
|
5a3f2004bc | ||
|
|
0e86fc8094 | ||
|
|
3c6c4b88ef | ||
|
|
4b4a10be7e | ||
|
|
4f1491858b | ||
|
|
33433b1aaf | ||
|
|
d85ebe0d34 | ||
|
|
235bd85510 | ||
|
|
5c29345f48 | ||
|
|
ebcb2b84ae | ||
|
|
080021e73d | ||
|
|
f447e28c86 | ||
|
|
f3b2ca644c | ||
|
|
4fdead0add | ||
|
|
059c6e9df7 | ||
|
|
a3f0efa8bc | ||
|
|
a79ad5abeb | ||
|
|
c6f8a14643 | ||
|
|
d262357c87 | ||
|
|
5da3730015 | ||
|
|
b444a4887b | ||
|
|
cc7133927f | ||
|
|
4b01042a4f | ||
|
|
4b94ef998d | ||
|
|
db5764b3d7 | ||
|
|
3c0115b71c | ||
|
|
c401c790bc | ||
|
|
271dcacf19 | ||
|
|
983d29d033 | ||
|
|
078f6e1cc4 | ||
|
|
9f8f6bc814 | ||
|
|
ef35178396 | ||
|
|
b3781c6d8f | ||
|
|
2319739f6d | ||
|
|
f9ac1ca698 | ||
|
|
9802bf2f37 | ||
|
|
843433e35d | ||
|
|
15c651ca96 | ||
|
|
4c1e56ccce | ||
|
|
906dfc680a | ||
|
|
583197f1c8 | ||
|
|
5fca3b1002 | ||
|
|
1edf5ae52a | ||
|
|
39f7f41e6d | ||
|
|
3139ad605f | ||
|
|
a9de069f6b | ||
|
|
4a8e2aab51 | ||
|
|
749c5f178c | ||
|
|
680989bc2e | ||
|
|
418989989b | ||
|
|
9a06a6ac10 | ||
|
|
ff7bae0bba | ||
|
|
215ee946dd | ||
|
|
cc13b9318d | ||
|
|
9d26eec6b0 | ||
|
|
bb969a0005 | ||
|
|
37b8fda889 | ||
|
|
0c9487a53d | ||
|
|
a039704f1b | ||
|
|
ec9be5aed6 | ||
|
|
542aacb002 | ||
|
|
e7e391f85c | ||
|
|
8ccd773b4b | ||
|
|
cb52a43940 | ||
|
|
2463809356 | ||
|
|
b71df4d8ed | ||
|
|
4cd60e818a | ||
|
|
31aad9471b | ||
|
|
34dac9e091 | ||
|
|
c1c81bb8f6 | ||
|
|
012350a8fd | ||
|
|
fefdda0015 | ||
|
|
55d63df17b | ||
|
|
37ee52aa8f | ||
|
|
da289017a5 | ||
|
|
0fb6f38bab | ||
|
|
014e9c8a26 | ||
|
|
d07c005191 | ||
|
|
a2b9930f92 | ||
|
|
8ac7b9addd | ||
|
|
8115f52d8a | ||
|
|
1581b82ae5 | ||
|
|
9a466cb86b | ||
|
|
fda2ac0a5a | ||
|
|
c1975eebdb | ||
|
|
ab891449cf | ||
|
|
e4f235586e | ||
|
|
49e79274bb | ||
|
|
4d077eba3c | ||
|
|
ad24071c9c | ||
|
|
d99af0c43d | ||
|
|
0f86e61cdd | ||
|
|
dea2d04a63 | ||
|
|
a34eb1ae57 | ||
|
|
6f426a870f | ||
|
|
0e129d4b10 | ||
|
|
2d65e7dc9e | ||
|
|
6e47c07aa3 | ||
|
|
dea159d766 | ||
|
|
e4221a9ca7 | ||
|
|
240a0285c2 | ||
|
|
bc69d0388c | ||
|
|
5043f157fa | ||
|
|
2b4877f122 | ||
|
|
53b9cf704c | ||
|
|
69dd7650e1 | ||
|
|
06b93cbdac | ||
|
|
dd21859cd1 | ||
|
|
9a19b33116 | ||
|
|
5385d0aaed | ||
|
|
02547975e9 | ||
|
|
598324b7aa | ||
|
|
49cd71daf7 | ||
|
|
2f2cd0af58 | ||
|
|
607238230d | ||
|
|
ed952dd891 | ||
|
|
50de6b1d71 | ||
|
|
4db293ccf2 | ||
|
|
3211406ef2 | ||
|
|
c93084e623 | ||
|
|
009715ccea | ||
|
|
c22f38fff2 | ||
|
|
e2c6ef40ec | ||
|
|
b44e2f8d8d | ||
|
|
c20bec7a13 | ||
|
|
f21874c546 | ||
|
|
fce8aea40b | ||
|
|
1dab955843 | ||
|
|
92c4d1f41a | ||
|
|
89e4f2f0aa | ||
|
|
cf0458b4a9 | ||
|
|
c524a8ce2b | ||
|
|
5df430e694 | ||
|
|
faaefd726a | ||
|
|
19451db485 | ||
|
|
abe38e7a2c | ||
|
|
495e0d261d | ||
|
|
cfcffab9d1 | ||
|
|
94ed2c6245 | ||
|
|
e3f01f75a2 | ||
|
|
5865a8160e | ||
|
|
619dd5fcd9 | ||
|
|
9310ddddaf | ||
|
|
25265a5f51 | ||
|
|
bdc8024ba9 | ||
|
|
116fdf5c11 | ||
|
|
49f301589d | ||
|
|
6d08d42626 | ||
|
|
3c7243fcba | ||
|
|
f6bb8757a4 | ||
|
|
2cd21c8331 | ||
|
|
edac7a3b4d | ||
|
|
64b73ba7cf | ||
|
|
e41e5dbaa9 | ||
|
|
00c77a77c9 | ||
|
|
378aa54500 | ||
|
|
3bc7874412 | ||
|
|
a4864b2b7a | ||
|
|
2b27ce88fe | ||
|
|
c32694ff5c | ||
|
|
97ae38c3fa | ||
|
|
9edec9cf04 | ||
|
|
dcaad44315 | ||
|
|
ddc8e9f31d | ||
|
|
6b07040ad7 | ||
|
|
ec3209bdb3 | ||
|
|
883a270f56 | ||
|
|
cbcc85dc93 | ||
|
|
aecd921c82 | ||
|
|
549fcde1e7 | ||
|
|
25430ebb10 | ||
|
|
e216c051d4 | ||
|
|
debeff618d | ||
|
|
30706e5a30 | ||
|
|
5957bd0fde | ||
|
|
425dfc5e50 | ||
|
|
6436dd9998 | ||
|
|
e75600c878 | ||
|
|
7545af2c15 | ||
|
|
5c65877bb3 | ||
|
|
09acb664c5 | ||
|
|
77d2dd96e1 | ||
|
|
2443ca3b0a | ||
|
|
c3f78c7b63 | ||
|
|
db1a3b2d47 | ||
|
|
65fb377f8e | ||
|
|
15035f5199 | ||
|
|
337d39076c | ||
|
|
3cd0463054 | ||
|
|
1556ce4830 | ||
|
|
7065f0797a | ||
|
|
afe9894632 | ||
|
|
35f95fd1fb | ||
|
|
a66e41b8f3 | ||
|
|
29a8202ce7 | ||
|
|
91cf1a1e6f | ||
|
|
16ed9c28bf | ||
|
|
578bc69498 | ||
|
|
f17f049f62 | ||
|
|
662b177acf | ||
|
|
31d91adb42 | ||
|
|
630d9c8eef | ||
|
|
97d3b0f2a9 | ||
|
|
0206e6173f | ||
|
|
ff104f543d | ||
|
|
ec3b5d7956 | ||
|
|
389cf12c1f | ||
|
|
39e81fb74b | ||
|
|
8818c584b8 | ||
|
|
f2891135f8 | ||
|
|
5a764f7fdf | ||
|
|
c129df4049 | ||
|
|
b93cbda566 | ||
|
|
f55137852d | ||
|
|
e9aa9d7420 | ||
|
|
113070b14e | ||
|
|
e66f4e5a83 | ||
|
|
3b33623a46 | ||
|
|
8d85b58ce4 | ||
|
|
dddd5b2e43 | ||
|
|
ff8e2d60d0 | ||
|
|
8c7bd73e8e | ||
|
|
0dc24effa9 | ||
|
|
809d7b2ad8 | ||
|
|
1119c96d5f | ||
|
|
e71add0c82 | ||
|
|
a4095d6833 | ||
|
|
5038326e36 | ||
|
|
c8800b9589 | ||
|
|
339ea56d18 | ||
|
|
76f6097847 | ||
|
|
71ff6dc3c5 | ||
|
|
25966fb186 | ||
|
|
488d2acb01 | ||
|
|
467c7ddeb7 | ||
|
|
bbb885e155 | ||
|
|
7a946b2096 | ||
|
|
08f8a9970e | ||
|
|
ec458fd04a | ||
|
|
2703cad2be | ||
|
|
79bea82c78 | ||
|
|
93e735306e | ||
|
|
bf940aae0f | ||
|
|
6bb795d622 | ||
|
|
afde0d7f3a | ||
|
|
d6d810fd42 | ||
|
|
7cc6ba75e9 | ||
|
|
250a3490ce | ||
|
|
0fdb80b04d | ||
|
|
b85b927add | ||
|
|
a4dbc6d85d | ||
|
|
0711dd746c | ||
|
|
90d1d6b110 | ||
|
|
5d85896a5f | ||
|
|
5cd27f4684 | ||
|
|
bd799912f6 | ||
|
|
9c1a3bc2ca | ||
|
|
22a0dc9df0 | ||
|
|
3112ba6c24 | ||
|
|
39b8821b08 | ||
|
|
75aaeacbb3 | ||
|
|
ff954f8716 | ||
|
|
1a4e0b2a93 | ||
|
|
4b27fdec7f | ||
|
|
a1d94898cb | ||
|
|
9fc886e151 | ||
|
|
5cfff24dbb | ||
|
|
cec0f89b24 | ||
|
|
13eb4f5385 | ||
|
|
1057e1263d | ||
|
|
f256b5ca81 | ||
|
|
103d78d0ee | ||
|
|
ad64fdeda6 | ||
|
|
7aea49c315 | ||
|
|
b3a1b4ee83 | ||
|
|
0ddd7f8c0d | ||
|
|
7b2c187590 | ||
|
|
620f555c90 | ||
|
|
0a5f6a4750 | ||
|
|
265172dd69 | ||
|
|
ee930cc905 | ||
|
|
f28a00fb58 | ||
|
|
5fe0e24839 | ||
|
|
8a6f73f9c2 |
2
.dockerignore
Normal file
2
.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
dist
|
||||||
|
bin
|
||||||
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: [bakito]
|
||||||
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,27 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. What version of AdGuardHome sync used?
|
|
||||||
2. What version of AdGuardHome us used?
|
|
||||||
3. How does the configuration look?
|
|
||||||
4. What is the error message?
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Screenshots**
|
|
||||||
If applicable, add log files or json responses from AdGuardHome to help explain your problem.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context about the problem here.
|
|
||||||
78
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
78
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
name: Bug report
|
||||||
|
description: Create a report to help us improve
|
||||||
|
labels: ['bug']
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this issue report! If you have usage questions, please try the [FAQ](https://github.com/bakito/adguardhome-sync/wiki/FAQ) first.
|
||||||
|
- type: textarea
|
||||||
|
id: what-happened
|
||||||
|
attributes:
|
||||||
|
label: What happened?
|
||||||
|
description: Also tell us, what did you expect to happen?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: adguardhome-sync-version
|
||||||
|
attributes:
|
||||||
|
label: AdguardHome-Sync Version
|
||||||
|
description: |
|
||||||
|
- What version of adguardhome-sync was running when you discovered this issue?
|
||||||
|
- Are you running the docker or binary version?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: adguardhome-version
|
||||||
|
attributes:
|
||||||
|
label: AdguardHome Version
|
||||||
|
description: What version of adguardhome was running when you discovered this issue?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: os-information
|
||||||
|
attributes:
|
||||||
|
label: OS Information
|
||||||
|
description: |
|
||||||
|
- What Operating System are you running? `cat /etc/os-release`
|
||||||
|
- What is the architecture of your CPU? `uname -m`
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: config
|
||||||
|
attributes:
|
||||||
|
label: Configuration
|
||||||
|
description: |
|
||||||
|
- How did you configure adguardhome-sync?
|
||||||
|
- Please provide your configuration
|
||||||
|
render: shell
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: applied-config
|
||||||
|
attributes:
|
||||||
|
label: Current Applied Configuration
|
||||||
|
description: |
|
||||||
|
- Run adguardhome-sync with environment variable `PRINT_CONFIG_ONLY=true` and provide the output here
|
||||||
|
render: shell
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: Relevant DEBUG log output
|
||||||
|
description: |
|
||||||
|
Please copy and paste any relevant **debug** log output. This will be automatically formatted into code, so no need for backticks.
|
||||||
|
Enable debug logs by defining the following environment variable `LOG_LEVEL=debug`.
|
||||||
|
|
||||||
|
Please also check adguardhome logs and paste any relevant logs/errors to this issue.
|
||||||
|
render: shell
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Anything else?
|
||||||
|
description: |
|
||||||
|
Links? References? Anything that will provide more context about the issue you are encountering!
|
||||||
|
|
||||||
|
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
10
.github/ISSUE_TEMPLATE/custom.md
vendored
10
.github/ISSUE_TEMPLATE/custom.md
vendored
@@ -1,10 +0,0 @@
|
|||||||
---
|
|
||||||
name: Custom issue template
|
|
||||||
about: Describe this issue template's purpose here.
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: ''
|
|
||||||
labels: enhancement
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context or screenshots about the feature request here.
|
|
||||||
36
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
36
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
name: Feature request
|
||||||
|
description: Suggest an idea for this project
|
||||||
|
labels: ['enhancement']
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this issue report!
|
||||||
|
- type: textarea
|
||||||
|
id: relation
|
||||||
|
attributes:
|
||||||
|
label: Is your feature request related to a problem? Please describe.
|
||||||
|
description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: solution
|
||||||
|
attributes:
|
||||||
|
label: Describe the solution you'd like*
|
||||||
|
description: |
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: alternatives
|
||||||
|
attributes:
|
||||||
|
label: Describe alternatives you've considered
|
||||||
|
description: |
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Anything else?
|
||||||
|
description: |
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
60
.github/ISSUE_TEMPLATE/general_issue.yaml
vendored
Normal file
60
.github/ISSUE_TEMPLATE/general_issue.yaml
vendored
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
name: General Issue
|
||||||
|
description: Report an issue with your setup
|
||||||
|
labels: []
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this issue report! If you have usage questions, please try the [FAQ](https://github.com/bakito/adguardhome-sync/wiki/FAQ) first.
|
||||||
|
- type: textarea
|
||||||
|
id: what-happened
|
||||||
|
attributes:
|
||||||
|
label: What happened?
|
||||||
|
description: Also tell us, what did you expect to happen?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: adguardhome-sync-version
|
||||||
|
attributes:
|
||||||
|
label: AdguardHome-Sync Version
|
||||||
|
description: What version of adguardhome-sync was running when you discovered this issue?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: adguardhome-version
|
||||||
|
attributes:
|
||||||
|
label: AdguardHome Version
|
||||||
|
description: What version of adguardhome was running when you discovered this issue?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: config
|
||||||
|
attributes:
|
||||||
|
label: Configuration
|
||||||
|
description: |
|
||||||
|
- How did you configure adguardhome-sync?
|
||||||
|
- Please provide your configuration
|
||||||
|
render: shell
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: Relevant log output
|
||||||
|
description: |
|
||||||
|
Please copy and paste any relevant **debug** log output. This will be automatically formatted into code, so no need for backticks.
|
||||||
|
Enable debug logs by defining the following environment variable `LOG_LEVEL=debug`.
|
||||||
|
|
||||||
|
Please also check adguardhome logs and paste any relevant logs/errors to this issue.
|
||||||
|
render: shell
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Anything else?
|
||||||
|
description: |
|
||||||
|
Links? References? Anything that will provide more context about the issue you are encountering!
|
||||||
|
|
||||||
|
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
11
.github/dependabot.yml
vendored
11
.github/dependabot.yml
vendored
@@ -1,11 +0,0 @@
|
|||||||
# To get started with Dependabot version updates, you'll need to specify which
|
|
||||||
# package ecosystems to update and where the package manifests are located.
|
|
||||||
# Please see the documentation for all configuration options:
|
|
||||||
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
|
||||||
|
|
||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: "gomod" # See documentation for possible values
|
|
||||||
directory: "/" # Location of package manifests
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
80
.github/workflows/codeql-analysis.yml
vendored
Normal file
80
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# For most projects, this workflow file will not need changing; you simply need
|
||||||
|
# to commit it to your repository.
|
||||||
|
#
|
||||||
|
# You may wish to alter this file to override the set of languages analyzed,
|
||||||
|
# or to provide custom queries or build logic.
|
||||||
|
#
|
||||||
|
# ******** NOTE ********
|
||||||
|
# We have attempted to detect the languages in your repository. Please check
|
||||||
|
# the `language` matrix defined below to confirm you have the correct set of
|
||||||
|
# supported CodeQL languages.
|
||||||
|
#
|
||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [ main ]
|
||||||
|
schedule:
|
||||||
|
- cron: '32 19 * * 6'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'go' ]
|
||||||
|
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||||
|
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version-file: "go.mod"
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v3
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v3
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 https://git.io/JvXDl
|
||||||
|
|
||||||
|
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||||
|
# and modify them (or add more) to build your code if your project
|
||||||
|
# uses a compiled language
|
||||||
|
|
||||||
|
#- run: |
|
||||||
|
# make bootstrap
|
||||||
|
# make release
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v3
|
||||||
89
.github/workflows/docker-images.yml
vendored
Normal file
89
.github/workflows/docker-images.yml
vendored
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
name: docker-images
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch: # allows manual triggering
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 * * *'
|
||||||
|
#pull_request:
|
||||||
|
# branches:
|
||||||
|
# - main
|
||||||
|
release:
|
||||||
|
types:
|
||||||
|
- published
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
images:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
build:
|
||||||
|
- fromImage: scratch
|
||||||
|
tagPrefix: ""
|
||||||
|
- fromImage: alpine:latest
|
||||||
|
tagPrefix: "alpine-"
|
||||||
|
steps:
|
||||||
|
- name: Get current date
|
||||||
|
run: echo "curr_date=$(date --utc +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_ENV
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Login to Quay
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: quay.io
|
||||||
|
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||||
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Modify Dockerfile
|
||||||
|
run: |
|
||||||
|
sed -i -e "s|FROM scratch|FROM ${{ matrix.build.fromImage }}|g" Dockerfile
|
||||||
|
|
||||||
|
- name: Build images ${{github.event.release.tag_name }}
|
||||||
|
id: docker_build_release
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
if: ${{ github.event.release.tag_name != '' }}
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
pull: true
|
||||||
|
push: true
|
||||||
|
tags: quay.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}latest,quay.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}${{ github.event.release.tag_name }},ghcr.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}latest,ghcr.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}${{ github.event.release.tag_name }}
|
||||||
|
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/arm64,linux/ppc64le
|
||||||
|
provenance: false
|
||||||
|
build-args: |
|
||||||
|
VERSION=${{ github.event.release.tag_name }}
|
||||||
|
BUILD=${{ env.curr_date }}
|
||||||
|
|
||||||
|
- name: Check for commits in the last 24 hours
|
||||||
|
run: echo "NEW_COMMIT_COUNT=$(git log --oneline --since '24 hours ago' | wc -l)" >> $GITHUB_ENV
|
||||||
|
if: ${{ github.event.release.tag_name == '' }}
|
||||||
|
|
||||||
|
- name: Build images
|
||||||
|
id: docker_build_main
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
if: ${{ github.event.release.tag_name == '' && env.NEW_COMMIT_COUNT > 0 }}
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
pull: true
|
||||||
|
push: ${{ github.ref == 'refs/heads/main' }}
|
||||||
|
tags: quay.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}main,ghcr.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}main
|
||||||
|
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/arm64,linux/ppc64le
|
||||||
|
provenance: false
|
||||||
|
build-args: |
|
||||||
|
VERSION=main
|
||||||
|
BUILD=${{ env.curr_date }}
|
||||||
|
|
||||||
|
- name: Image digest
|
||||||
|
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||||
55
.github/workflows/e2e.yaml
vendored
Normal file
55
.github/workflows/e2e.yaml
vendored
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
name: e2e tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
e2e:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
build:
|
||||||
|
- mode: env
|
||||||
|
protocol: https
|
||||||
|
- mode: file
|
||||||
|
protocol: http
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Setup jq
|
||||||
|
uses: dcarbone/install-jq-action@v3
|
||||||
|
|
||||||
|
- name: Install kind with registry
|
||||||
|
uses: bakito/kind-with-registry-action@main
|
||||||
|
|
||||||
|
- name: Build image
|
||||||
|
run: ./testdata/e2e/bin/build-image.sh
|
||||||
|
|
||||||
|
- name: Install Helm Chart
|
||||||
|
run: ./testdata/e2e/bin/install-chart.sh ${{ matrix.build.mode }}
|
||||||
|
- name: Wait for pod to start
|
||||||
|
run: ./testdata/e2e/bin/wait-for-start.sh ${{ matrix.build.protocol }}
|
||||||
|
- name: Show origin pre Logs
|
||||||
|
run: ./testdata/e2e/bin/show-origin-logs.sh pre
|
||||||
|
- name: Wait for sync to finish
|
||||||
|
run: ./testdata/e2e/bin/wait-for-sync.sh ${{ matrix.build.protocol }}
|
||||||
|
- name: Show origin post Logs
|
||||||
|
run: ./testdata/e2e/bin/show-origin-logs.sh post
|
||||||
|
- name: Show Replica Logs
|
||||||
|
run: ./testdata/e2e/bin/show-replica-logs.sh
|
||||||
|
- name: Show Sync Logs
|
||||||
|
run: ./testdata/e2e/bin/show-sync-logs.sh
|
||||||
|
- name: Show Sync Metrics
|
||||||
|
run: ./testdata/e2e/bin/show-sync-metrics.sh ${{ matrix.build.protocol }}
|
||||||
|
- name: Read latest replica config
|
||||||
|
run: ./testdata/e2e/bin/read-latest-replica-config.sh
|
||||||
59
.github/workflows/go.yml
vendored
59
.github/workflows/go.yml
vendored
@@ -6,36 +6,52 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main ]
|
branches: [ main ]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
golangci:
|
golangci:
|
||||||
name: lint
|
name: lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v5
|
||||||
- name: golangci-lint
|
|
||||||
uses: golangci/golangci-lint-action@v2
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
|
go-version-file: "go.mod"
|
||||||
version: v1.36
|
|
||||||
|
- name: Lint
|
||||||
|
run: make lint
|
||||||
|
|
||||||
test:
|
test:
|
||||||
name: test
|
name: test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Set up Go 1.x
|
|
||||||
uses: actions/setup-go@v2
|
|
||||||
with:
|
|
||||||
go-version: ^1.16
|
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version-file: "go.mod"
|
||||||
|
|
||||||
|
- name: Model
|
||||||
|
run: make model
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: make test
|
run: make test-ci
|
||||||
|
|
||||||
- name: Send coverage
|
- name: Send coverage
|
||||||
|
if: runner.os == 'Linux'
|
||||||
uses: shogo82148/actions-goveralls@v1
|
uses: shogo82148/actions-goveralls@v1
|
||||||
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
path-to-profile: coverage.out
|
path-to-profile: coverage.out
|
||||||
|
|
||||||
@@ -44,16 +60,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Set up Go 1.x
|
|
||||||
uses: actions/setup-go@v2
|
|
||||||
with:
|
|
||||||
go-version: ^1.16
|
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version-file: "go.mod"
|
||||||
|
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v2
|
run: make test-release
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
args: --skip-publish --snapshot --rm-dist
|
|
||||||
|
|||||||
58
.github/workflows/publish.yml
vendored
58
.github/workflows/publish.yml
vendored
@@ -1,58 +0,0 @@
|
|||||||
name: docker-image
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
release:
|
|
||||||
types:
|
|
||||||
- published
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
main:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Get current date
|
|
||||||
id: date
|
|
||||||
run: echo "::set-output name=date::$(date --utc +%Y-%m-%dT%H:%M:%SZ)"
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
- name: Login to Quay
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
registry: quay.io
|
|
||||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
|
||||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
|
||||||
- name: Login to ghcr.io
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Build and push ${{github.event.release.tag_name }}
|
|
||||||
id: docker_build_release
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
if: ${{ github.event.release.tag_name != '' }}
|
|
||||||
with:
|
|
||||||
push: true
|
|
||||||
tags: quay.io/bakito/adguardhome-sync:latest,quay.io/bakito/adguardhome-sync:${{ github.event.release.tag_name }},ghcr.io/bakito/adguardhome-sync:latest,ghcr.io/bakito/adguardhome-sync:${{ github.event.release.tag_name }}
|
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
|
||||||
build-args: |
|
|
||||||
VERSION=${{ github.event.release.tag_name }}
|
|
||||||
BUILD=${{ steps.date.outputs.date }}
|
|
||||||
|
|
||||||
- name: Build and push main
|
|
||||||
id: docker_build_main
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
if: ${{ github.event.release.tag_name == '' }}
|
|
||||||
with:
|
|
||||||
push: true
|
|
||||||
tags: quay.io/bakito/adguardhome-sync:main,ghcr.io/bakito/adguardhome-sync:main
|
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
|
||||||
build-args: |
|
|
||||||
VERSION=main
|
|
||||||
BUILD=${{ steps.date.outputs.date }}
|
|
||||||
- name: Image digest
|
|
||||||
run: echo ${{ steps.docker_build.outputs.digest }}
|
|
||||||
17
.github/workflows/stale.yml
vendored
Normal file
17
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
name: Mark stale issues and pull requests
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 0 * * *"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v10
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
stale-issue-message: 'This issue has been inactive for 60 days. If the issue is still relevant please comment to re-activate the issue. If no action is taken within 7 days, the issue will be marked closed.'
|
||||||
|
stale-pr-message: 'This pull request has been inactive for 60 days. If the pull request is still relevant please comment to re-activate the pull request. If no action is taken within 7 days, the pull request will be marked closed.'
|
||||||
16
.github/workflows/virustotal.yaml
vendored
Normal file
16
.github/workflows/virustotal.yaml
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
name: Scan GitHub Release with VirusTotal
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [released]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
scan_release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Analyze Build Assets
|
||||||
|
uses: bakito/virustotal-action@main
|
||||||
|
with:
|
||||||
|
release_name: ${{github.event.release.tag_name}}
|
||||||
|
vt_api_key: ${{secrets.VT_API_KEY}}
|
||||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -1,6 +1,15 @@
|
|||||||
|
.vscode
|
||||||
.idea
|
.idea
|
||||||
coverage.out
|
.fleet
|
||||||
|
.run
|
||||||
|
coverage.out*
|
||||||
dist
|
dist
|
||||||
adguardhome-sync
|
adguardhome-sync
|
||||||
main
|
main
|
||||||
.adguardhome-sync.yaml
|
.adguardhome-sync.yaml
|
||||||
|
tmp
|
||||||
|
bin
|
||||||
|
!testdata/e2e/bin
|
||||||
|
config*.yaml
|
||||||
|
*.log
|
||||||
|
wiki
|
||||||
|
|||||||
215
.golangci.yaml
Normal file
215
.golangci.yaml
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
version: '2'
|
||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- asciicheck
|
||||||
|
- bidichk
|
||||||
|
- bodyclose
|
||||||
|
- canonicalheader
|
||||||
|
- containedctx
|
||||||
|
- copyloopvar
|
||||||
|
- decorder
|
||||||
|
- dogsled
|
||||||
|
- dupword
|
||||||
|
- durationcheck
|
||||||
|
- err113
|
||||||
|
- errname
|
||||||
|
- errorlint
|
||||||
|
- exptostd
|
||||||
|
- fatcontext
|
||||||
|
- forcetypeassert
|
||||||
|
- gocheckcompilerdirectives
|
||||||
|
- gochecksumtype
|
||||||
|
- gocritic
|
||||||
|
- godot
|
||||||
|
- gomodguard
|
||||||
|
- goprintffuncname
|
||||||
|
- gosmopolitan
|
||||||
|
- grouper
|
||||||
|
- iface
|
||||||
|
- importas
|
||||||
|
- inamedparam
|
||||||
|
- interfacebloat
|
||||||
|
- intrange
|
||||||
|
- loggercheck
|
||||||
|
- makezero
|
||||||
|
- mirror
|
||||||
|
- misspell
|
||||||
|
- nilerr
|
||||||
|
- nilnesserr
|
||||||
|
- noctx
|
||||||
|
- nolintlint
|
||||||
|
- nosprintfhostport
|
||||||
|
- perfsprint
|
||||||
|
- predeclared
|
||||||
|
- promlinter
|
||||||
|
- protogetter
|
||||||
|
- reassign
|
||||||
|
- revive
|
||||||
|
- rowserrcheck
|
||||||
|
- sloglint
|
||||||
|
- spancheck
|
||||||
|
- sqlclosecheck
|
||||||
|
- staticcheck
|
||||||
|
- tagalign
|
||||||
|
- testableexamples
|
||||||
|
- testifylint
|
||||||
|
- thelper
|
||||||
|
- unconvert
|
||||||
|
- unparam
|
||||||
|
- usestdlibvars
|
||||||
|
- usetesting
|
||||||
|
- wastedassign
|
||||||
|
- whitespace
|
||||||
|
- zerologlint
|
||||||
|
disable:
|
||||||
|
- asasalint
|
||||||
|
- contextcheck
|
||||||
|
- cyclop
|
||||||
|
- depguard
|
||||||
|
- dupl
|
||||||
|
- errchkjson
|
||||||
|
- exhaustive
|
||||||
|
- exhaustruct
|
||||||
|
- forbidigo
|
||||||
|
- funlen
|
||||||
|
- ginkgolinter
|
||||||
|
- gochecknoglobals
|
||||||
|
- gochecknoinits
|
||||||
|
- gocognit
|
||||||
|
- goconst
|
||||||
|
- gocyclo
|
||||||
|
- godox
|
||||||
|
- goheader
|
||||||
|
- gomoddirectives
|
||||||
|
- gosec
|
||||||
|
- ireturn
|
||||||
|
- lll
|
||||||
|
- maintidx
|
||||||
|
- musttag
|
||||||
|
- nakedret
|
||||||
|
- nestif
|
||||||
|
- nilnil
|
||||||
|
- nlreturn
|
||||||
|
- nonamedreturns
|
||||||
|
- paralleltest
|
||||||
|
- prealloc
|
||||||
|
- recvcheck
|
||||||
|
- tagliatelle
|
||||||
|
- testpackage
|
||||||
|
- tparallel
|
||||||
|
- varnamelen
|
||||||
|
- wrapcheck
|
||||||
|
- wsl
|
||||||
|
settings:
|
||||||
|
gocritic:
|
||||||
|
enable-all: true
|
||||||
|
disabled-checks:
|
||||||
|
- emptyFallthrough
|
||||||
|
- hugeParam
|
||||||
|
- rangeValCopy
|
||||||
|
- unnamedResult
|
||||||
|
- whyNoLint
|
||||||
|
govet:
|
||||||
|
disable:
|
||||||
|
- fieldalignment
|
||||||
|
- shadow
|
||||||
|
enable-all: true
|
||||||
|
misspell:
|
||||||
|
locale: US
|
||||||
|
revive:
|
||||||
|
enable-all-rules: true
|
||||||
|
rules:
|
||||||
|
- name: add-constant
|
||||||
|
disabled: true
|
||||||
|
- name: cognitive-complexity
|
||||||
|
disabled: true
|
||||||
|
- name: cyclomatic
|
||||||
|
disabled: true
|
||||||
|
- name: deep-exit
|
||||||
|
disabled: true
|
||||||
|
- name: dot-imports
|
||||||
|
severity: warning
|
||||||
|
disabled: false
|
||||||
|
exclude: [""]
|
||||||
|
arguments:
|
||||||
|
- allowedPackages: ["github.com/onsi/ginkgo/v2", "github.com/onsi/gomega"]
|
||||||
|
- name: empty-block
|
||||||
|
disabled: true
|
||||||
|
- name: exported
|
||||||
|
disabled: true
|
||||||
|
- name: filename-format
|
||||||
|
arguments:
|
||||||
|
- ^[a-z][-0-9_a-z]*(?:\.gen)?\.go$
|
||||||
|
- name: flag-parameter
|
||||||
|
disabled: true
|
||||||
|
- name: function-length
|
||||||
|
disabled: true
|
||||||
|
- name: function-result-limit
|
||||||
|
disabled: true
|
||||||
|
- name: import-shadowing
|
||||||
|
disabled: true
|
||||||
|
- name: line-length-limit
|
||||||
|
disabled: true
|
||||||
|
- name: max-control-nesting
|
||||||
|
disabled: true
|
||||||
|
- name: max-public-structs
|
||||||
|
disabled: true
|
||||||
|
- name: nested-structs
|
||||||
|
disabled: true
|
||||||
|
- name: package-comments
|
||||||
|
disabled: true
|
||||||
|
- name: unused-parameter
|
||||||
|
disabled: true
|
||||||
|
- name: unused-receiver
|
||||||
|
disabled: true
|
||||||
|
- name: var-naming
|
||||||
|
disabled: true
|
||||||
|
- name: enforce-switch-style
|
||||||
|
disabled: true
|
||||||
|
- name: blank-imports
|
||||||
|
disabled: true
|
||||||
|
staticcheck:
|
||||||
|
checks:
|
||||||
|
- 'all'
|
||||||
|
- '-ST1000'
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
presets:
|
||||||
|
- common-false-positives
|
||||||
|
- legacy
|
||||||
|
- std-error-handling
|
||||||
|
rules:
|
||||||
|
- linters:
|
||||||
|
- err113
|
||||||
|
text: do not define dynamic errors, use wrapped static errors instead
|
||||||
|
- linters:
|
||||||
|
- forbidigo
|
||||||
|
path: ^internal/cmds/
|
||||||
|
- linters:
|
||||||
|
- forcetypeassert
|
||||||
|
path: _test\.go$
|
||||||
|
- linters:
|
||||||
|
- forbidigo
|
||||||
|
path: assets/scripts/generate-commit.go
|
||||||
|
formatters:
|
||||||
|
enable:
|
||||||
|
- gci
|
||||||
|
- gofmt
|
||||||
|
- gofumpt
|
||||||
|
- goimports
|
||||||
|
- golines
|
||||||
|
settings:
|
||||||
|
gci:
|
||||||
|
sections:
|
||||||
|
- standard
|
||||||
|
- default
|
||||||
|
- prefix(github.com/bakito/adguardhome-sync)
|
||||||
|
gofumpt:
|
||||||
|
module-path: github.com/bakito/adguardhome-sync
|
||||||
|
extra-rules: true
|
||||||
|
goimports:
|
||||||
|
local-prefixes:
|
||||||
|
- github.com/bakito/adguardhome-sync
|
||||||
|
golines:
|
||||||
|
max-len: 128
|
||||||
|
tab-len: 4
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
version: 2
|
||||||
# This is an example goreleaser.yaml file with some sane defaults.
|
# This is an example goreleaser.yaml file with some sane defaults.
|
||||||
# Make sure to check the documentation at http://goreleaser.com
|
# Make sure to check the documentation at http://goreleaser.com
|
||||||
builds:
|
builds:
|
||||||
@@ -9,6 +10,7 @@ builds:
|
|||||||
- linux
|
- linux
|
||||||
- windows
|
- windows
|
||||||
- darwin
|
- darwin
|
||||||
|
- openbsd
|
||||||
goarch:
|
goarch:
|
||||||
- 386
|
- 386
|
||||||
- amd64
|
- amd64
|
||||||
@@ -27,21 +29,24 @@ builds:
|
|||||||
goarch: arm
|
goarch: arm
|
||||||
- goos: windows
|
- goos: windows
|
||||||
goarch: arm64
|
goarch: arm64
|
||||||
|
- goos: openbsd
|
||||||
|
goarch: 386
|
||||||
|
- goos: openbsd
|
||||||
|
goarch: arm
|
||||||
hooks:
|
hooks:
|
||||||
post: upx {{ .Path }}
|
post:
|
||||||
archives:
|
# don't upx windows binaries as they make trouble with virus scanners
|
||||||
- replacements:
|
- bash -c 'if [[ "{{ .Path }}" != *.exe ]] && [[ "{{ .Path }}" != *darwin* ]] && [[ "{{ .Path }}" != *openbsd* ]]; then upx {{ .Path }}; fi'
|
||||||
386: i386
|
|
||||||
amd64: x86_64
|
|
||||||
checksum:
|
checksum:
|
||||||
name_template: 'checksums.txt'
|
name_template: 'checksums.txt'
|
||||||
snapshot:
|
snapshot:
|
||||||
name_template: "{{ .Tag }}-next"
|
version_template: "{{ .Tag }}-next"
|
||||||
changelog:
|
changelog:
|
||||||
sort: asc
|
sort: asc
|
||||||
filters:
|
filters:
|
||||||
exclude:
|
exclude:
|
||||||
- '^docs:'
|
- '^docs:'
|
||||||
- '^test:'
|
- '^test:'
|
||||||
|
- '^chore'
|
||||||
release:
|
release:
|
||||||
prerelease: auto
|
prerelease: auto
|
||||||
3
.oapi-codegen.yaml
Normal file
3
.oapi-codegen.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
output-options:
|
||||||
|
client-type-name: AdguardHomeClient
|
||||||
|
response-type-suffix: Resp
|
||||||
92
.toolbox.mk
Normal file
92
.toolbox.mk
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
## toolbox - start
|
||||||
|
## Generated with https://github.com/bakito/toolbox
|
||||||
|
|
||||||
|
## Current working directory
|
||||||
|
TB_LOCALDIR ?= $(shell which cygpath > /dev/null 2>&1 && cygpath -m $$(pwd) || pwd)
|
||||||
|
## Location to install dependencies to
|
||||||
|
TB_LOCALBIN ?= $(TB_LOCALDIR)/bin
|
||||||
|
$(TB_LOCALBIN):
|
||||||
|
if [ ! -e $(TB_LOCALBIN) ]; then mkdir -p $(TB_LOCALBIN); fi
|
||||||
|
|
||||||
|
# Helper functions
|
||||||
|
STRIP_V = $(patsubst v%,%,$(1))
|
||||||
|
|
||||||
|
## Tool Binaries
|
||||||
|
TB_CONTROLLER_GEN ?= $(TB_LOCALBIN)/controller-gen
|
||||||
|
TB_GINKGO ?= $(TB_LOCALBIN)/ginkgo
|
||||||
|
TB_GOLANGCI_LINT ?= $(TB_LOCALBIN)/golangci-lint
|
||||||
|
TB_GORELEASER ?= $(TB_LOCALBIN)/goreleaser
|
||||||
|
TB_MOCKGEN ?= $(TB_LOCALBIN)/mockgen
|
||||||
|
TB_OAPI_CODEGEN ?= $(TB_LOCALBIN)/oapi-codegen
|
||||||
|
TB_SEMVER ?= $(TB_LOCALBIN)/semver
|
||||||
|
|
||||||
|
## Tool Versions
|
||||||
|
# renovate: packageName=github.com/kubernetes-sigs/controller-tools
|
||||||
|
TB_CONTROLLER_GEN_VERSION ?= v0.19.0
|
||||||
|
# renovate: packageName=github.com/golangci/golangci-lint/v2
|
||||||
|
TB_GOLANGCI_LINT_VERSION ?= v2.4.0
|
||||||
|
TB_GOLANGCI_LINT_VERSION_NUM ?= $(call STRIP_V,$(TB_GOLANGCI_LINT_VERSION))
|
||||||
|
# renovate: packageName=github.com/goreleaser/goreleaser/v2
|
||||||
|
TB_GORELEASER_VERSION ?= v2.12.0
|
||||||
|
TB_GORELEASER_VERSION_NUM ?= $(call STRIP_V,$(TB_GORELEASER_VERSION))
|
||||||
|
# renovate: packageName=github.com/uber-go/mock
|
||||||
|
TB_MOCKGEN_VERSION ?= v0.6.0
|
||||||
|
# renovate: packageName=github.com/oapi-codegen/oapi-codegen/v2
|
||||||
|
TB_OAPI_CODEGEN_VERSION ?= v2.5.0
|
||||||
|
# renovate: packageName=github.com/bakito/semver
|
||||||
|
TB_SEMVER_VERSION ?= v1.1.7
|
||||||
|
TB_SEMVER_VERSION_NUM ?= $(call STRIP_V,$(TB_SEMVER_VERSION))
|
||||||
|
|
||||||
|
## Tool Installer
|
||||||
|
.PHONY: tb.controller-gen
|
||||||
|
tb.controller-gen: ## Download controller-gen locally if necessary.
|
||||||
|
@test -s $(TB_CONTROLLER_GEN) || \
|
||||||
|
GOBIN=$(TB_LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(TB_CONTROLLER_GEN_VERSION)
|
||||||
|
.PHONY: tb.ginkgo
|
||||||
|
tb.ginkgo: ## Download ginkgo locally if necessary.
|
||||||
|
@test -s $(TB_GINKGO) || \
|
||||||
|
GOBIN=$(TB_LOCALBIN) go install github.com/onsi/ginkgo/v2/ginkgo
|
||||||
|
.PHONY: tb.golangci-lint
|
||||||
|
tb.golangci-lint: ## Download golangci-lint locally if necessary.
|
||||||
|
@test -s $(TB_GOLANGCI_LINT) && $(TB_GOLANGCI_LINT) --version | grep -q $(TB_GOLANGCI_LINT_VERSION_NUM) || \
|
||||||
|
GOBIN=$(TB_LOCALBIN) go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(TB_GOLANGCI_LINT_VERSION)
|
||||||
|
.PHONY: tb.goreleaser
|
||||||
|
tb.goreleaser: ## Download goreleaser locally if necessary.
|
||||||
|
@test -s $(TB_GORELEASER) && $(TB_GORELEASER) --version | grep -q $(TB_GORELEASER_VERSION_NUM) || \
|
||||||
|
GOBIN=$(TB_LOCALBIN) go install github.com/goreleaser/goreleaser/v2@$(TB_GORELEASER_VERSION)
|
||||||
|
.PHONY: tb.mockgen
|
||||||
|
tb.mockgen: ## Download mockgen locally if necessary.
|
||||||
|
@test -s $(TB_MOCKGEN) || \
|
||||||
|
GOBIN=$(TB_LOCALBIN) go install go.uber.org/mock/mockgen@$(TB_MOCKGEN_VERSION)
|
||||||
|
.PHONY: tb.oapi-codegen
|
||||||
|
tb.oapi-codegen: ## Download oapi-codegen locally if necessary.
|
||||||
|
@test -s $(TB_OAPI_CODEGEN) || \
|
||||||
|
GOBIN=$(TB_LOCALBIN) go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@$(TB_OAPI_CODEGEN_VERSION)
|
||||||
|
.PHONY: tb.semver
|
||||||
|
tb.semver: ## Download semver locally if necessary.
|
||||||
|
@test -s $(TB_SEMVER) && $(TB_SEMVER) -version | grep -q $(TB_SEMVER_VERSION_NUM) || \
|
||||||
|
GOBIN=$(TB_LOCALBIN) go install github.com/bakito/semver@$(TB_SEMVER_VERSION)
|
||||||
|
|
||||||
|
## Reset Tools
|
||||||
|
.PHONY: tb.reset
|
||||||
|
tb.reset:
|
||||||
|
@rm -f \
|
||||||
|
$(TB_CONTROLLER_GEN) \
|
||||||
|
$(TB_GINKGO) \
|
||||||
|
$(TB_GOLANGCI_LINT) \
|
||||||
|
$(TB_GORELEASER) \
|
||||||
|
$(TB_MOCKGEN) \
|
||||||
|
$(TB_OAPI_CODEGEN) \
|
||||||
|
$(TB_SEMVER)
|
||||||
|
|
||||||
|
## Update Tools
|
||||||
|
.PHONY: tb.update
|
||||||
|
tb.update: tb.reset
|
||||||
|
toolbox makefile --renovate -f $(TB_LOCALDIR)/Makefile \
|
||||||
|
sigs.k8s.io/controller-tools/cmd/controller-gen@github.com/kubernetes-sigs/controller-tools \
|
||||||
|
github.com/golangci/golangci-lint/v2/cmd/golangci-lint?--version \
|
||||||
|
github.com/goreleaser/goreleaser/v2?--version \
|
||||||
|
go.uber.org/mock/mockgen@github.com/uber-go/mock \
|
||||||
|
github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen \
|
||||||
|
github.com/bakito/semver?-version
|
||||||
|
## toolbox - end
|
||||||
17
Dockerfile
17
Dockerfile
@@ -1,21 +1,22 @@
|
|||||||
FROM docker.io/library/golang:1.16 as builder
|
FROM golang:1.25-alpine AS builder
|
||||||
|
|
||||||
WORKDIR /go/src/app
|
WORKDIR /go/src/app
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y upx
|
RUN apk update && apk add upx ca-certificates tzdata
|
||||||
|
|
||||||
ARG VERSION=main
|
ARG VERSION=main
|
||||||
ARG BUILD="N/A"
|
ARG BUILD="N/A"
|
||||||
|
|
||||||
ENV GOPROXY=https://goproxy.io \
|
ENV GO111MODULE=on \
|
||||||
GO111MODULE=on \
|
|
||||||
CGO_ENABLED=0 \
|
CGO_ENABLED=0 \
|
||||||
GOOS=linux
|
GOOS=linux
|
||||||
|
|
||||||
ADD . /go/src/app/
|
COPY . /go/src/app/
|
||||||
|
|
||||||
RUN go build -a -installsuffix cgo -ldflags="-w -s -X github.com/bakito/adguardhome-sync/version.Version=${VERSION} -X github.com/bakito/adguardhome-sync/version.Build=${BUILD}" -o adguardhome-sync . \
|
|
||||||
&& upx -q adguardhome-sync
|
RUN go build -a -installsuffix cgo -ldflags="-w -s -X github.com/bakito/adguardhome-sync/version.Version=${VERSION} -X github.com/bakito/adguardhome-sync/version.Build=${BUILD}" -o adguardhome-sync .
|
||||||
|
|
||||||
|
RUN go version && upx -q adguardhome-sync
|
||||||
|
|
||||||
# application image
|
# application image
|
||||||
FROM scratch
|
FROM scratch
|
||||||
@@ -25,5 +26,7 @@ LABEL maintainer="bakito <github@bakito.ch>"
|
|||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
ENTRYPOINT ["/opt/go/adguardhome-sync"]
|
ENTRYPOINT ["/opt/go/adguardhome-sync"]
|
||||||
CMD ["run", "--config", "/config/adguardhome-sync.yaml"]
|
CMD ["run", "--config", "/config/adguardhome-sync.yaml"]
|
||||||
|
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||||
|
COPY --from=builder /usr/share/zoneinfo/ /usr/share/zoneinfo/
|
||||||
COPY --from=builder /go/src/app/adguardhome-sync /opt/go/adguardhome-sync
|
COPY --from=builder /go/src/app/adguardhome-sync /opt/go/adguardhome-sync
|
||||||
USER 1001
|
USER 1001
|
||||||
|
|||||||
105
Makefile
105
Makefile
@@ -1,42 +1,91 @@
|
|||||||
# Run go fmt against code
|
# Include toolbox tasks
|
||||||
fmt:
|
include ./.toolbox.mk
|
||||||
go fmt ./...
|
|
||||||
gofmt -s -w .
|
|
||||||
|
|
||||||
# Run go vet against code
|
# Run go lint against code
|
||||||
vet:
|
lint: tb.golangci-lint
|
||||||
go vet ./...
|
$(TB_GOLANGCI_LINT) run --fix
|
||||||
|
|
||||||
# Run golangci-lint
|
|
||||||
lint:
|
|
||||||
golangci-lint run
|
|
||||||
|
|
||||||
# Run go mod tidy
|
# Run go mod tidy
|
||||||
tidy:
|
tidy:
|
||||||
go mod tidy
|
go mod tidy
|
||||||
|
|
||||||
|
generate: model mocks deepcopy-gen
|
||||||
|
deepcopy-gen: tb.controller-gen
|
||||||
|
@mkdir -p ./tmp
|
||||||
|
@touch ./tmp/deepcopy-gen-boilerplate.go.txt
|
||||||
|
$(TB_CONTROLLER_GEN) paths=./internal/types object
|
||||||
|
|
||||||
|
.PHONY: docs
|
||||||
|
docs:
|
||||||
|
go run cmd/docs/main.go
|
||||||
|
|
||||||
# Run tests
|
# Run tests
|
||||||
test: mocks tidy fmt vet
|
test: generate lint test-ci
|
||||||
go test ./... -coverprofile=coverage.out
|
|
||||||
|
fuzz:
|
||||||
|
go test -fuzz=FuzzMask -v ./internal/types/ -fuzztime=60s
|
||||||
|
|
||||||
|
# Run ci tests
|
||||||
|
test-ci: mocks tidy tb.ginkgo
|
||||||
|
$(TB_GINKGO) --cover --coverprofile coverage.out.tmp ./...
|
||||||
|
cat coverage.out.tmp | grep -v "_generated.go" > coverage.out
|
||||||
go tool cover -func=coverage.out
|
go tool cover -func=coverage.out
|
||||||
|
|
||||||
mocks: mockgen
|
mocks: tb.mockgen
|
||||||
mockgen -destination pkg/mocks/client/mock.go github.com/bakito/adguardhome-sync/pkg/client Client
|
$(TB_MOCKGEN) -package client -destination internal/mocks/client/mock.go github.com/bakito/adguardhome-sync/internal/client Client
|
||||||
|
$(TB_MOCKGEN) -package client -destination internal/mocks/flags/mock.go github.com/bakito/adguardhome-sync/internal/config Flags
|
||||||
|
|
||||||
release: semver
|
release: tb.semver tb.goreleaser
|
||||||
@version=$$(semver); \
|
@version=$$($(TB_SEMVER)); \
|
||||||
git tag -s $$version -m"Release $$version"
|
git tag -s $$version -m"Release $$version"
|
||||||
goreleaser --rm-dist
|
$(TB_GORELEASER) --clean --parallelism 2
|
||||||
|
|
||||||
test-release:
|
test-release: tb.goreleaser
|
||||||
goreleaser --skip-publish --snapshot --rm-dist
|
$(TB_GORELEASER) --skip=publish --snapshot --clean --parallelism 2
|
||||||
|
|
||||||
semver:
|
start-replica:
|
||||||
ifeq (, $(shell which semver))
|
docker rm -f adguardhome-replica
|
||||||
$(shell go get -u github.com/bakito/semver)
|
docker run --pull always --name adguardhome-replica -p 9091:3000 --rm adguard/adguardhome:latest
|
||||||
endif
|
# docker run --pull always --name adguardhome-replica -p 9090:80 -p 9091:3000 --rm adguard/adguardhome:v0.107.13
|
||||||
|
|
||||||
mockgen:
|
copy-replica-config:
|
||||||
ifeq (, $(shell which mockgen))
|
docker cp adguardhome-replica:/opt/adguardhome/conf/AdGuardHome.yaml tmp/AdGuardHome.yaml
|
||||||
$(shell go get github.com/golang/mock/mockgen@v1.5)
|
|
||||||
endif
|
start-replica2:
|
||||||
|
docker rm -f adguardhome-replica2
|
||||||
|
docker run --pull always --name adguardhome-replica2 -p 9093:3000 --rm adguard/adguardhome:latest
|
||||||
|
# docker run --pull always --name adguardhome-replica -p 9090:80 -p 9091:3000 --rm adguard/adguardhome:v0.107.13
|
||||||
|
|
||||||
|
check_defined = \
|
||||||
|
$(strip $(foreach 1,$1, \
|
||||||
|
$(call __check_defined,$1,$(strip $(value 2)))))
|
||||||
|
__check_defined = \
|
||||||
|
$(if $(value $1),, \
|
||||||
|
$(error Undefined $1$(if $2, ($2))))
|
||||||
|
|
||||||
|
build-image:
|
||||||
|
$(call check_defined, AGH_SYNC_VERSION)
|
||||||
|
docker build --build-arg VERSION=${AGH_SYNC_VERSION} --build-arg BUILD=$(shell date -u +'%Y-%m-%dT%H:%M:%S.%3NZ') --name adgardhome-replica -t ghcr.io/bakito/adguardhome-sync:${AGH_SYNC_VERSION} .
|
||||||
|
|
||||||
|
kind-create:
|
||||||
|
kind delete cluster
|
||||||
|
kind create cluster
|
||||||
|
|
||||||
|
kind-test:
|
||||||
|
@./testdata/e2e/bin/install-chart.sh
|
||||||
|
|
||||||
|
# renovate: packageName=AdguardTeam/AdGuardHome
|
||||||
|
ADGUARD_HOME_VERSION ?= v0.107.66
|
||||||
|
|
||||||
|
model: tb.oapi-codegen
|
||||||
|
@mkdir -p tmp
|
||||||
|
go run cmd/openapi/main.go $(ADGUARD_HOME_VERSION)
|
||||||
|
$(TB_OAPI_CODEGEN) -package model -generate types,client -config .oapi-codegen.yaml tmp/schema.yaml > internal/client/model/model_generated.go
|
||||||
|
|
||||||
|
model-diff:
|
||||||
|
go run cmd/openapi/main.go $(ADGUARD_HOME_VERSION)
|
||||||
|
go run cmd/openapi/main.go
|
||||||
|
diff tmp/schema.yaml tmp/schema-master.yaml
|
||||||
|
|
||||||
|
zellij:
|
||||||
|
zellij -l ./testdata/test-layout.kdl
|
||||||
|
|||||||
306
README.md
306
README.md
@@ -1,10 +1,19 @@
|
|||||||
[](https://github.com/bakito/adguardhome-sync/actions/workflows/go.yml)
|
[](https://github.com/bakito/adguardhome-sync/actions/workflows/go.yml)
|
||||||
|
[](https://github.com/bakito/adguardhome-sync/actions/workflows/e2e.yaml)
|
||||||
[](https://goreportcard.com/report/github.com/bakito/adguardhome-sync)
|
[](https://goreportcard.com/report/github.com/bakito/adguardhome-sync)
|
||||||
[](https://coveralls.io/github/bakito/adguardhome-sync?branch=main)
|
[](https://coveralls.io/github/bakito/adguardhome-sync?branch=main)
|
||||||
|
|
||||||
# AdGuardHome sync
|
|
||||||
|
|
||||||
Synchronize [AdGuardHome](https://github.com/AdguardTeam/AdGuardHome) config to a replica instance.
|
|
||||||
|
# <img src="./media/adguardhome-sync.svg" alt="AdGuardHome sync" width="50"/> AdGuardHome sync
|
||||||
|
|
||||||
|
Synchronize [AdGuardHome](https://github.com/AdguardTeam/AdGuardHome) config to replica instances.
|
||||||
|
|
||||||
|
## FAQ & Deprecations
|
||||||
|
|
||||||
|
Please check the wiki
|
||||||
|
for [FAQ](https://github.com/bakito/adguardhome-sync/wiki/FAQ)
|
||||||
|
and [Deprecations](https://github.com/bakito/adguardhome-sync/wiki/Deprecations).
|
||||||
|
|
||||||
## Current sync features
|
## Current sync features
|
||||||
|
|
||||||
@@ -15,6 +24,9 @@ Synchronize [AdGuardHome](https://github.com/AdguardTeam/AdGuardHome) config to
|
|||||||
- Clients
|
- Clients
|
||||||
- DNS Config
|
- DNS Config
|
||||||
- DHCP Config
|
- DHCP Config
|
||||||
|
- Theme
|
||||||
|
|
||||||
|
By default, all features are enabled. Single features can be disabled in the config.
|
||||||
|
|
||||||
### Setup of initial instances
|
### Setup of initial instances
|
||||||
|
|
||||||
@@ -25,30 +37,120 @@ To skip automatic setup
|
|||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
|
Get from [releases](https://github.com/bakito/adguardhome-sync/releases) or install from source
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go get -u github.com/bakito/adguardhome-sync
|
go install github.com/bakito/adguardhome-sync@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
Both the origin instance must be initially setup via the AdguardHome installation wizard.
|
Both the origin instance and replica(s) must be initially set up with AdguardHome via the AdguardHome installation wizard.
|
||||||
|
|
||||||
## Run
|
## Username / Password vs. Cookie
|
||||||
|
|
||||||
|
Some instances of AdGuard Home do not support basic authentication. For instance, many routers with built-in Adguard
|
||||||
|
Home support do not. If this is the case, a valid cookie may be provided instead. If the router protects the AdGuard
|
||||||
|
instance behind its own authentication, the cookie from an authenticated request may allow the sync to succeed.
|
||||||
|
|
||||||
|
- This has been tested successfully against GL.Inet routers with AdGuard Home.
|
||||||
|
- Note: due to the short validity of cookies, this approach is likely only suitable for one-time syncs
|
||||||
|
|
||||||
|
## Run Linux/Mac
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
||||||
|
export LOG_LEVEL=info
|
||||||
export ORIGIN_URL=https://192.168.1.2:3000
|
export ORIGIN_URL=https://192.168.1.2:3000
|
||||||
export ORIGIN_USERNAME=username
|
export ORIGIN_USERNAME=username
|
||||||
export ORIGIN_PASSWORD=password
|
export ORIGIN_PASSWORD=password
|
||||||
export REPLICA_URL=http://192.168.1.3
|
# export ORIGIN_COOKIE=Origin-Cookie-Name=CCCOOOKKKIIIEEE
|
||||||
export REPLICA_USERNAME=username
|
export REPLICA1_URL=http://192.168.1.3
|
||||||
export REPLICA_PASSWORD=password
|
export REPLICA1_USERNAME=username
|
||||||
|
export REPLICA1_PASSWORD=password
|
||||||
|
# export REPLICA_COOKIE=Replica-Cookie-Name=CCCOOOKKKIIIEEE
|
||||||
|
|
||||||
# run once
|
# run once
|
||||||
adguardhome-sync run
|
adguardhome-sync run
|
||||||
|
|
||||||
# run as daemon
|
# run as daemon
|
||||||
adguardhome-sync run --cron "*/10 * * * *"
|
adguardhome-sync run --cron "0 */2 * * *"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run as Linux Service via Systemd
|
||||||
|
|
||||||
|
> Verified on Ubuntu Linux 24.04
|
||||||
|
|
||||||
|
Assume you have downloaded the the `adguardhome-sync` binary to `/opt/adguardhome-sync`.
|
||||||
|
|
||||||
|
Create systemd service file `/opt/adguardhome-sync/adguardhome-sync.service`:
|
||||||
|
|
||||||
|
```
|
||||||
|
[Unit]
|
||||||
|
Description = AdGuardHome Sync
|
||||||
|
After = network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart = /opt/adguardhome-sync/adguardhome-sync --config /opt/adguardhome-sync/adguardhome-sync.yaml run
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy = multi-user.target
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a configuration file `/opt/adguardhome-sync/adguardhome-sync.yaml`, please follow [Config file](#config-file-1)
|
||||||
|
section below for details.
|
||||||
|
|
||||||
|
Install and enable service:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo cp /opt/adguardhome-sync/adguardhome-sync.service /etc/systemd/system/
|
||||||
|
|
||||||
|
sudo systemctl enable adguardhome-sync.service
|
||||||
|
|
||||||
|
sudo systemctl start adguardhome-sync.service
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Then you can check the status:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl status adguardhome-sync.service
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
If web UI has been enabled in configuration (default port is 8080), can also check the status via
|
||||||
|
`http://<server-IP>:8080`
|
||||||
|
|
||||||
|
## Run Windows
|
||||||
|
|
||||||
|
```bash
|
||||||
|
@ECHO OFF
|
||||||
|
@TITLE AdGuardHome-Sync
|
||||||
|
|
||||||
|
REM set LOG_LEVEL=debug
|
||||||
|
set LOG_LEVEL=info
|
||||||
|
REM set LOG_LEVEL=warn
|
||||||
|
REM set LOG_LEVEL=error
|
||||||
|
|
||||||
|
set ORIGIN_URL=http://192.168.1.2:3000
|
||||||
|
set ORIGIN_USERNAME=username
|
||||||
|
set ORIGIN_PASSWORD=password
|
||||||
|
# set ORIGIN_COOKIE=Origin-Cookie-Name=CCCOOOKKKIIIEEE
|
||||||
|
|
||||||
|
set REPLICA1_URL=http://192.168.2.2:3000
|
||||||
|
set REPLICA1_USERNAME=username
|
||||||
|
set REPLICA1_PASSWORD=password
|
||||||
|
# set REPLICA1_COOKIE=Replica-Cookie-Name=CCCOOOKKKIIIEEE
|
||||||
|
|
||||||
|
set FEATURES_DHCP_SERVER_CONFIG=false
|
||||||
|
set FEATURES_DHCP_STATIC_LEASES=false
|
||||||
|
|
||||||
|
# run once
|
||||||
|
adguardhome-sync run
|
||||||
|
|
||||||
|
# run as daemon
|
||||||
|
adguardhome-sync run --cron "0 */2 * * *"
|
||||||
```
|
```
|
||||||
|
|
||||||
## docker cli
|
## docker cli
|
||||||
@@ -59,7 +161,7 @@ docker run -d \
|
|||||||
-p 8080:8080 \
|
-p 8080:8080 \
|
||||||
-v /path/to/appdata/config/adguardhome-sync.yaml:/config/adguardhome-sync.yaml \
|
-v /path/to/appdata/config/adguardhome-sync.yaml:/config/adguardhome-sync.yaml \
|
||||||
--restart unless-stopped \
|
--restart unless-stopped \
|
||||||
quay.io/bakito/adguardhome-sync:latest
|
ghcr.io/bakito/adguardhome-sync:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
## docker compose
|
## docker compose
|
||||||
@@ -71,8 +173,9 @@ docker run -d \
|
|||||||
version: "2.1"
|
version: "2.1"
|
||||||
services:
|
services:
|
||||||
adguardhome-sync:
|
adguardhome-sync:
|
||||||
image: quay.io/bakito/adguardhome-sync
|
image: ghcr.io/bakito/adguardhome-sync
|
||||||
container_name: adguardhome-sync
|
container_name: adguardhome-sync
|
||||||
|
command: run --config /config/adguardhome-sync.yaml
|
||||||
volumes:
|
volumes:
|
||||||
- /path/to/appdata/config/adguardhome-sync.yaml:/config/adguardhome-sync.yaml
|
- /path/to/appdata/config/adguardhome-sync.yaml:/config/adguardhome-sync.yaml
|
||||||
ports:
|
ports:
|
||||||
@@ -80,34 +183,70 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
```
|
```
|
||||||
|
|
||||||
### env
|
## Config via environment variables
|
||||||
|
|
||||||
```yaml
|
For Replicas replace `#` with the index number for the replica. E.g.: `REPLICA#_URL` -> `REPLICA1_URL`
|
||||||
---
|
<!-- env-doc-start -->
|
||||||
version: "2.1"
|
| Name | Type | Description |
|
||||||
services:
|
| :--- | ---- |:----------- |
|
||||||
adguardhome-sync:
|
| ORIGIN_URL (string) | string | URL of adguardhome instance |
|
||||||
image: quay.io/bakito/adguardhome-sync
|
| ORIGIN_WEB_URL (string) | string | Web URL of adguardhome instance |
|
||||||
container_name: adguardhome-sync
|
| ORIGIN_API_PATH (string) | string | API Path |
|
||||||
command: run
|
| ORIGIN_USERNAME (string) | string | Adguardhome username |
|
||||||
environment:
|
| ORIGIN_PASSWORD (string) | string | Adguardhome password |
|
||||||
- ORIGIN_URL=https://192.168.1.2:3000
|
| ORIGIN_COOKIE (string) | string | Adguardhome cookie |
|
||||||
- ORIGIN_USERNAME=username
|
| ORIGIN_REQUEST_HEADERS (map) | map | Request Headers 'key1:value1,key2:value2' |
|
||||||
- ORIGIN_PASSWORD=password
|
| ORIGIN_INSECURE_SKIP_VERIFY (bool) | bool | Skip TLS verification |
|
||||||
- REPLICA_URL=http://192.168.1.3
|
| ORIGIN_AUTO_SETUP (bool) | bool | Automatically setup the instance if it is not initialized |
|
||||||
- REPLICA_USERNAME=username
|
| ORIGIN_INTERFACE_NAME (string) | string | Network interface name |
|
||||||
- REPLICA_PASSWORD=password
|
| ORIGIN_DHCP_SERVER_ENABLED (bool) | bool | Enable DHCP server |
|
||||||
- REPLICA1_URL=http://192.168.1.4
|
| REPLICA#_URL (string) | string | URL of adguardhome instance |
|
||||||
- REPLICA1_USERNAME=username
|
| REPLICA#_WEB_URL (string) | string | Web URL of adguardhome instance |
|
||||||
- REPLICA1_PASSWORD=password
|
| REPLICA#_API_PATH (string) | string | API Path |
|
||||||
- REPLICA1_APIPATH=/some/path/control
|
| REPLICA#_USERNAME (string) | string | Adguardhome username |
|
||||||
# - REPLICA1_AUTOSETUP=true # if true, AdGuardHome is automatically initialized.
|
| REPLICA#_PASSWORD (string) | string | Adguardhome password |
|
||||||
- CRON=*/10 * * * * # run every 10 minutes
|
| REPLICA#_COOKIE (string) | string | Adguardhome cookie |
|
||||||
- RUNONSTART=true
|
| REPLICA#_REQUEST_HEADERS (map) | map | Request Headers 'key1:value1,key2:value2' |
|
||||||
ports:
|
| REPLICA#_INSECURE_SKIP_VERIFY (bool) | bool | Skip TLS verification |
|
||||||
- 8080:8080
|
| REPLICA#_AUTO_SETUP (bool) | bool | Automatically setup the instance if it is not initialized |
|
||||||
restart: unless-stopped
|
| REPLICA#_INTERFACE_NAME (string) | string | Network interface name |
|
||||||
```
|
| REPLICA#_DHCP_SERVER_ENABLED (bool) | bool | Enable DHCP server |
|
||||||
|
| CRON (string) | string | Cron expression for the sync interval |
|
||||||
|
| RUN_ON_START (bool) | bool | Run the sync on startup |
|
||||||
|
| PRINT_CONFIG_ONLY (bool) | bool | Print current config only and stop the application |
|
||||||
|
| CONTINUE_ON_ERROR (bool) | bool | Continue sync on errors |
|
||||||
|
| API_PORT (int) | int | API port (API is disabled if port is set to 0) |
|
||||||
|
| API_USERNAME (string) | string | API username |
|
||||||
|
| API_PASSWORD (string) | string | API password |
|
||||||
|
| API_DARK_MODE (bool) | bool | API dark mode |
|
||||||
|
| API_METRICS_ENABLED (bool) | bool | Enable metrics |
|
||||||
|
| API_METRICS_SCRAPE_INTERVAL (int64) | int64 | Interval for metrics scraping |
|
||||||
|
| API_METRICS_QUERY_LOG_LIMIT (int) | int | Metrics log query limit |
|
||||||
|
| API_TLS_CERT_DIR (string) | string | API TLS certificate directory |
|
||||||
|
| API_TLS_CERT_NAME (string) | string | API TLS certificate file name |
|
||||||
|
| API_TLS_KEY_NAME (string) | string | API TLS key file name |
|
||||||
|
| FEATURES_DNS_ACCESS_LISTS (bool) | bool | Sync DNS access lists |
|
||||||
|
| FEATURES_DNS_SERVER_CONFIG (bool) | bool | Sync DNS server config |
|
||||||
|
| FEATURES_DNS_REWRITES (bool) | bool | Sync DNS rewrites |
|
||||||
|
| FEATURES_DHCP_SERVER_CONFIG (bool) | bool | Sync DHCP server config |
|
||||||
|
| FEATURES_DHCP_STATIC_LEASES (bool) | bool | Sync DHCP static leases |
|
||||||
|
| FEATURES_GENERAL_SETTINGS (bool) | bool | Sync general settings |
|
||||||
|
| FEATURES_QUERY_LOG_CONFIG (bool) | bool | Sync query log config |
|
||||||
|
| FEATURES_STATS_CONFIG (bool) | bool | Sync stats config |
|
||||||
|
| FEATURES_CLIENT_SETTINGS (bool) | bool | Sync client settings |
|
||||||
|
| FEATURES_SERVICES (bool) | bool | Sync services |
|
||||||
|
| FEATURES_FILTERS (bool) | bool | Sync filters |
|
||||||
|
| FEATURES_THEME (bool) | bool | Sync the web UI theme |
|
||||||
|
| FEATURES_TLS_CONFIG (bool) | bool | Sync the TLS config |
|
||||||
|
<!-- env-doc-end -->
|
||||||
|
|
||||||
|
### Unraid
|
||||||
|
|
||||||
|
⚠️ Disclaimer: Tere exists an unraid tepmlate for this application. This template is not managed by this project.
|
||||||
|
Also, as unraid is not known to me, I can not give any support on unraind templates.
|
||||||
|
|
||||||
|
Note when running the Docker container in Unraid please remove unneeded env variables if don't needed.
|
||||||
|
If replica2 isn't used this can cause sync errors.
|
||||||
|
|
||||||
### Config file
|
### Config file
|
||||||
|
|
||||||
@@ -115,11 +254,14 @@ location: $HOME/.adguardhome-sync.yaml
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# cron expression to run in daemon mode. (default; "" = runs only once)
|
# cron expression to run in daemon mode. (default; "" = runs only once)
|
||||||
cron: "*/10 * * * *"
|
cron: "0 */2 * * *"
|
||||||
|
|
||||||
# runs the synchronisation on startup
|
# runs the synchronisation on startup
|
||||||
runOnStart: true
|
runOnStart: true
|
||||||
|
|
||||||
|
# If enabled, the synchronisation task will not fail on single errors, but will log the errors and continue
|
||||||
|
continueOnError: false
|
||||||
|
|
||||||
origin:
|
origin:
|
||||||
# url of the origin instance
|
# url of the origin instance
|
||||||
url: https://192.168.1.2:3000
|
url: https://192.168.1.2:3000
|
||||||
@@ -127,24 +269,25 @@ origin:
|
|||||||
# insecureSkipVerify: true # disable tls check
|
# insecureSkipVerify: true # disable tls check
|
||||||
username: username
|
username: username
|
||||||
password: password
|
password: password
|
||||||
|
# cookie: Origin-Cookie-Name=CCCOOOKKKIIIEEE
|
||||||
|
# requestHeaders: # Additional request headers
|
||||||
|
# AAA: bbb
|
||||||
|
|
||||||
# replica instance (optional, if only one)
|
# replicas instances
|
||||||
replica:
|
|
||||||
# url of the replica instance
|
|
||||||
url: http://192.168.1.3
|
|
||||||
username: username
|
|
||||||
password: password
|
|
||||||
|
|
||||||
# replicas instances (optional, if more than one)
|
|
||||||
replicas:
|
replicas:
|
||||||
# url of the replica instance
|
# url of the replica instance
|
||||||
- url: http://192.168.1.3
|
- url: http://192.168.1.3
|
||||||
username: username
|
username: username
|
||||||
password: password
|
password: password
|
||||||
|
# cookie: Replica1-Cookie-Name=CCCOOOKKKIIIEEE
|
||||||
- url: http://192.168.1.4
|
- url: http://192.168.1.4
|
||||||
username: username
|
username: username
|
||||||
password: password
|
password: password
|
||||||
|
# cookie: Replica2-Cookie-Name=CCCOOOKKKIIIEEE
|
||||||
# autoSetup: true # if true, AdGuardHome is automatically initialized.
|
# autoSetup: true # if true, AdGuardHome is automatically initialized.
|
||||||
|
# webURL: "https://some-other.url" # used in the web interface (default: <replica-url>
|
||||||
|
# requestHeaders: # Additional request headers
|
||||||
|
# AAA: bbb
|
||||||
|
|
||||||
# Configure the sync API server, disabled if api port is 0
|
# Configure the sync API server, disabled if api port is 0
|
||||||
api:
|
api:
|
||||||
@@ -153,12 +296,67 @@ api:
|
|||||||
# if username and password are defined, basic auth is applied to the sync API
|
# if username and password are defined, basic auth is applied to the sync API
|
||||||
username: username
|
username: username
|
||||||
password: password
|
password: password
|
||||||
|
# enable api dark mode
|
||||||
|
darkMode: true
|
||||||
|
|
||||||
|
# enable metrics on path '/metrics' (api port must be != 0)
|
||||||
|
# metrics:
|
||||||
|
# enabled: true
|
||||||
|
# scrapeInterval: 30s
|
||||||
|
# queryLogLimit: 10000
|
||||||
|
|
||||||
|
# enable tls for the api server
|
||||||
|
# tls:
|
||||||
|
# # the directory of the provided tls certs
|
||||||
|
# certDir: /path/to/certs
|
||||||
|
# # the name of the cert file (default: tls.crt)
|
||||||
|
# certName: foo.crt
|
||||||
|
# # the name of the key file (default: tls.key)
|
||||||
|
# keyName: bar.key
|
||||||
|
|
||||||
|
# Configure sync features; by default all features are enabled.
|
||||||
|
features:
|
||||||
|
generalSettings: true
|
||||||
|
queryLogConfig: true
|
||||||
|
statsConfig: true
|
||||||
|
clientSettings: true
|
||||||
|
services: true
|
||||||
|
filters: true
|
||||||
|
dhcp:
|
||||||
|
serverConfig: true
|
||||||
|
staticLeases: true
|
||||||
|
dns:
|
||||||
|
serverConfig: true
|
||||||
|
accessLists: true
|
||||||
|
rewrites: true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Home Assistant AdGuard Home Add-on users
|
||||||
|
|
||||||
|
To enable syncing with a Home Assistant instance using
|
||||||
|
the [AdGuard Home Add-on](https://github.com/hassio-addons/addon-adguard-home), you will need to enable the disabled
|
||||||
|
ports, under the Network heading
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
And then set the port of your choice for the Web interface
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Don't forget to save and restart the add-on.
|
||||||
|
|
||||||
|
Depending on your setup, you may also need to disable SSL for the add-on.
|
||||||
|
|
||||||
|
The username:password required for the Home Assistant replica is the one you use to login to your instance, however it's
|
||||||
|
recommended to setup a new local only user with minimal permissions.
|
||||||
|
|
||||||
|
All credit for this method goes to [Brunty](https://github.com/brunty) who has a far
|
||||||
|
more [detailed write up](https://brunty.me/post/replicate-adguard-home-settings-into-home-assistant-adguard-home-addon/)
|
||||||
|
about this on his blog.
|
||||||
|
|
||||||
## Log Level
|
## Log Level
|
||||||
|
|
||||||
The log level can be set with the environment variable: LOG_LEVEL
|
The log level can be set with the environment variable: `LOG_LEVEL`
|
||||||
|
|
||||||
The following log levels are supported (default: info)
|
The following log levels are supported (default: info)
|
||||||
|
|
||||||
@@ -166,3 +364,13 @@ The following log levels are supported (default: info)
|
|||||||
- info
|
- info
|
||||||
- warn
|
- warn
|
||||||
- error
|
- error
|
||||||
|
|
||||||
|
## Log Format
|
||||||
|
|
||||||
|
Default log format is `console`.
|
||||||
|
It can be changed to `json` by setting the environment variable: `LOG_FORMAT=json`.
|
||||||
|
|
||||||
|
## Video Tutorials
|
||||||
|
|
||||||
|
- [Como replicar la configuración de tu servidor DNS Adguard automáticamente - Tu servidor Part #12](https://www.youtube.com/watch?v=1LPeu_JG064) (
|
||||||
|
Spanish) by [Jonatan Castro](https://github.com/jcastro)
|
||||||
|
|||||||
96
cmd/docs/main.go
Normal file
96
cmd/docs/main.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
// Print the available environment variables
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Read the README.md file
|
||||||
|
content, err := os.ReadFile("README.md")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to string for easier manipulation
|
||||||
|
fileContent := string(content)
|
||||||
|
|
||||||
|
// Generate the environment variables documentation
|
||||||
|
var buf strings.Builder
|
||||||
|
_, _ = buf.WriteString("| Name | Type | Description |\n")
|
||||||
|
_, _ = buf.WriteString("| :--- | ---- |:----------- |\n")
|
||||||
|
printEnvTags(&buf, reflect.TypeOf(types.Config{}), "")
|
||||||
|
|
||||||
|
// Find the markers and replace content between them
|
||||||
|
startMarker := "<!-- env-doc-start -->"
|
||||||
|
endMarker := "<!-- env-doc-end -->"
|
||||||
|
|
||||||
|
start := strings.Index(fileContent, startMarker)
|
||||||
|
end := strings.Index(fileContent, endMarker)
|
||||||
|
|
||||||
|
if start == -1 || end == -1 {
|
||||||
|
log.Fatal("Could not find markers in README.md")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct new content
|
||||||
|
newContent := fileContent[:start+len(startMarker)] + "\n" + buf.String() + fileContent[end:]
|
||||||
|
|
||||||
|
// Write back to README.md
|
||||||
|
err = os.WriteFile("README.md", []byte(newContent), 0o644)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// printEnvTags recursively prints all fields with `env` tags.
|
||||||
|
func printEnvTags(w io.Writer, t reflect.Type, prefix string) {
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
if t.Kind() != reflect.Struct {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, field := range reflect.VisibleFields(t) {
|
||||||
|
if field.PkgPath != "" { // unexported field
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
envTag := field.Tag.Get("env")
|
||||||
|
if envTag == "" {
|
||||||
|
switch field.Name {
|
||||||
|
case "Origin":
|
||||||
|
envTag = "ORIGIN"
|
||||||
|
case "Replica":
|
||||||
|
envTag = "REPLICA#"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
combinedTag := envTag
|
||||||
|
if prefix != "" && envTag != "" {
|
||||||
|
combinedTag = prefix + "_" + envTag
|
||||||
|
} else if prefix != "" {
|
||||||
|
combinedTag = prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
ft := field.Type
|
||||||
|
if ft.Kind() == reflect.Ptr {
|
||||||
|
ft = ft.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if ft.Kind() == reflect.Struct && ft.Name() != "Time" { // skip time.Time
|
||||||
|
printEnvTags(w, ft, strings.TrimSuffix(combinedTag, "_"))
|
||||||
|
} else if envTag != "" {
|
||||||
|
envVar := strings.Trim(combinedTag, "_") + " (" + ft.Kind().String() + ")"
|
||||||
|
docs := field.Tag.Get("documentation")
|
||||||
|
|
||||||
|
_, _ = fmt.Fprintf(w, "| %s | %s | %s |\n", envVar, ft.Kind().String(), docs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
106
cmd/openapi/main.go
Normal file
106
cmd/openapi/main.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
version := "master"
|
||||||
|
fileName := "schema-master.yaml"
|
||||||
|
if len(os.Args) > 1 {
|
||||||
|
version = os.Args[1]
|
||||||
|
fileName = "schema.yaml"
|
||||||
|
}
|
||||||
|
log.Printf("Patching schema version %s\n", version)
|
||||||
|
|
||||||
|
ctx := context.Background() // Or use context.WithTimeout
|
||||||
|
req, err := http.NewRequestWithContext(
|
||||||
|
ctx,
|
||||||
|
http.MethodGet,
|
||||||
|
fmt.Sprintf("https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/%s/openapi/openapi.yaml", version),
|
||||||
|
http.NoBody,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
schema := make(map[string]any)
|
||||||
|
err = yaml.Unmarshal(data, &schema)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if requestBodies, ok, _ := unstructured.NestedMap(schema, "components", "requestBodies"); ok {
|
||||||
|
for k := range requestBodies {
|
||||||
|
_ = unstructured.SetNestedField(schema, k+"Body", "components", "requestBodies", k, "x-go-name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dnsInfo, ok, _ := unstructured.NestedMap(schema,
|
||||||
|
"paths", "/dns_info", "get", "responses", "200", "content", "application/json", "schema"); ok {
|
||||||
|
if allOf, ok, _ := unstructured.NestedSlice(dnsInfo, "allOf"); ok && len(allOf) == 2 {
|
||||||
|
delete(dnsInfo, "allOf")
|
||||||
|
//nolint:forcetypeassert
|
||||||
|
if err := unstructured.SetNestedMap(schema, allOf[0].(map[string]any),
|
||||||
|
"paths", "/dns_info", "get", "responses", "200", "content", "application/json", "schema"); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
correctEntries(schema)
|
||||||
|
|
||||||
|
addFakeTags(schema)
|
||||||
|
|
||||||
|
b, err := yaml.Marshal(&schema)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("Writing schema file tmp/%s", fileName)
|
||||||
|
err = os.WriteFile("tmp/"+fileName, b, 0o600)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func correctEntries(schema map[string]any) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func addFakeTags(schema map[string]any) {
|
||||||
|
fake := map[string]any{"faker": `slice_len=24`}
|
||||||
|
if err := unstructured.SetNestedMap(schema, fake, "components", "schemas", "Stats", "properties", "blocked_filtering", "x-oapi-codegen-extra-tags"); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
if err := unstructured.SetNestedMap(schema, fake, "components", "schemas", "Stats", "properties", "dns_queries", "x-oapi-codegen-extra-tags"); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
if err := unstructured.SetNestedMap(schema, fake, "components", "schemas", "Stats", "properties", "replaced_parental", "x-oapi-codegen-extra-tags"); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
if err := unstructured.SetNestedMap(schema, fake, "components", "schemas", "Stats", "properties", "replaced_safebrowsing", "x-oapi-codegen-extra-tags"); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
114
cmd/root.go
114
cmd/root.go
@@ -3,69 +3,35 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/log"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
|
||||||
"github.com/mitchellh/go-homedir"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
"github.com/bakito/adguardhome-sync/internal/log"
|
||||||
configCron = "cron"
|
"github.com/bakito/adguardhome-sync/version"
|
||||||
configRunOnStart = "runOnStart"
|
|
||||||
configBeta = "beta"
|
|
||||||
|
|
||||||
configAPIPort = "api.port"
|
|
||||||
configAPIUsername = "api.username"
|
|
||||||
configAPIPassword = "api.password"
|
|
||||||
|
|
||||||
configOriginURL = "origin.url"
|
|
||||||
configOriginAPIPath = "origin.apiPath"
|
|
||||||
configOriginUsername = "origin.username"
|
|
||||||
configOriginPassword = "origin.password"
|
|
||||||
configOriginInsecureSkipVerify = "origin.insecureSkipVerify"
|
|
||||||
|
|
||||||
configReplicaURL = "replica.url"
|
|
||||||
configReplicaAPIPath = "replica.apiPath"
|
|
||||||
configReplicaUsername = "replica.username"
|
|
||||||
configReplicaPassword = "replica.password"
|
|
||||||
configReplicaInsecureSkipVerify = "replica.insecureSkipVerify"
|
|
||||||
configReplicaAutoSetup = "replica.autoSetup"
|
|
||||||
|
|
||||||
envReplicasUsernameFormat = "REPLICA%s_USERNAME"
|
|
||||||
envReplicasPasswordFormat = "REPLICA%s_PASSWORD"
|
|
||||||
envReplicasAPIPathFormat = "REPLICA%s_APIPATH"
|
|
||||||
envReplicasInsecureSkipVerifyFormat = "REPLICA%s_INSECURESKIPVERIFY"
|
|
||||||
envReplicasAutoSetup = "REPLICA%s_AUTOSETUP"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cfgFile string
|
cfgFile string
|
||||||
logger = log.GetLogger("root")
|
logger = log.GetLogger("root")
|
||||||
envReplicasURLPattern = regexp.MustCompile(`^REPLICA(\d+)_URL=(.*)`)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// rootCmd represents the base command when called without any subcommands
|
// rootCmd represents the base command when called without any subcommands.
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
Use: "adguardhome-sync",
|
Use: "adguardhome-sync",
|
||||||
Short: "Synchronize config from one AdGuardHome instance to another",
|
Short: "Synchronize config from one AdGuardHome instance to another",
|
||||||
|
Version: version.Version,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||||
func Execute() {
|
func Execute() {
|
||||||
if err := rootCmd.Execute(); err != nil {
|
if err := rootCmd.Execute(); err != nil {
|
||||||
fmt.Println(err)
|
_, _ = fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cobra.OnInitialize(initConfig)
|
|
||||||
|
|
||||||
// Here you will define your flags and configuration settings.
|
// Here you will define your flags and configuration settings.
|
||||||
// Cobra supports persistent flags, which, if defined here,
|
// Cobra supports persistent flags, which, if defined here,
|
||||||
// will be global for your application.
|
// will be global for your application.
|
||||||
@@ -76,65 +42,3 @@ func init() {
|
|||||||
// when this action is called directly.
|
// when this action is called directly.
|
||||||
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||||
}
|
}
|
||||||
|
|
||||||
// initConfig reads in config file and ENV variables if set.
|
|
||||||
func initConfig() {
|
|
||||||
if cfgFile != "" {
|
|
||||||
// Use config file from the flag.
|
|
||||||
viper.SetConfigFile(cfgFile)
|
|
||||||
} else {
|
|
||||||
// Find home directory.
|
|
||||||
home, err := homedir.Dir()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search config in home directory with name ".adguardhome-sync" (without extension).
|
|
||||||
viper.AddConfigPath(home)
|
|
||||||
viper.SetConfigName(".adguardhome-sync")
|
|
||||||
}
|
|
||||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_"))
|
|
||||||
viper.AutomaticEnv() // read in environment variables that match
|
|
||||||
|
|
||||||
// If a config file is found, read it in.
|
|
||||||
if err := viper.ReadInConfig(); err == nil {
|
|
||||||
logger.Info("Using config file:", viper.ConfigFileUsed())
|
|
||||||
} else if cfgFile != "" {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getConfig() (*types.Config, error) {
|
|
||||||
cfg := &types.Config{}
|
|
||||||
if err := viper.Unmarshal(cfg); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cfg.Replicas) == 0 {
|
|
||||||
cfg.Replicas = append(cfg.Replicas, collectEnvReplicas()...)
|
|
||||||
}
|
|
||||||
return cfg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manually collect replicas from env.
|
|
||||||
func collectEnvReplicas() []types.AdGuardInstance {
|
|
||||||
var replicas []types.AdGuardInstance
|
|
||||||
for _, v := range os.Environ() {
|
|
||||||
if envReplicasURLPattern.MatchString(v) {
|
|
||||||
sm := envReplicasURLPattern.FindStringSubmatch(v)
|
|
||||||
re := types.AdGuardInstance{
|
|
||||||
URL: sm[2],
|
|
||||||
Username: os.Getenv(fmt.Sprintf(envReplicasUsernameFormat, sm[1])),
|
|
||||||
Password: os.Getenv(fmt.Sprintf(envReplicasPasswordFormat, sm[1])),
|
|
||||||
APIPath: os.Getenv(fmt.Sprintf(envReplicasAPIPathFormat, sm[1])),
|
|
||||||
InsecureSkipVerify: strings.EqualFold(os.Getenv(fmt.Sprintf(envReplicasInsecureSkipVerifyFormat, sm[1])), "true"),
|
|
||||||
AutoSetup: strings.EqualFold(os.Getenv(fmt.Sprintf(envReplicasAutoSetup, sm[1])), "true"),
|
|
||||||
}
|
|
||||||
replicas = append(replicas, re)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return replicas
|
|
||||||
}
|
|
||||||
|
|||||||
109
cmd/run.go
109
cmd/run.go
@@ -1,66 +1,93 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bakito/adguardhome-sync/pkg/log"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/sync"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/config"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/log"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// runCmd represents the run command
|
// runCmd represents the run command.
|
||||||
var doCmd = &cobra.Command{
|
var doCmd = &cobra.Command{
|
||||||
Use: "run",
|
Use: "run",
|
||||||
Short: "Start a synchronisation from origin to replica",
|
Short: "Start a synchronization from origin to replica",
|
||||||
Long: `Synchronizes the configuration form an origin instance to a replica`,
|
Long: `Synchronizes the configuration form an origin instance to a replica`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
logger = log.GetLogger("run")
|
logger = log.GetLogger("run")
|
||||||
cfg, err := getConfig()
|
cfg, err := config.Get(cfgFile, cmd.Flags())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return sync.Sync(cfg)
|
if err := cfg.Init(); err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.PrintConfigOnly() {
|
||||||
|
if err := cfg.Print(); err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return sync.Sync(cfg.Get())
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(doCmd)
|
rootCmd.AddCommand(doCmd)
|
||||||
doCmd.PersistentFlags().String("cron", "", "The cron expression to run in daemon mode")
|
doCmd.PersistentFlags().String(config.FlagCron, "", "The cron expression to run in daemon mode")
|
||||||
_ = viper.BindPFlag(configCron, doCmd.PersistentFlags().Lookup("cron"))
|
doCmd.PersistentFlags().Bool(config.FlagRunOnStart, true, "Run the sync job on start.")
|
||||||
doCmd.PersistentFlags().Bool("runOnStart", true, "Run the sync job on start.")
|
doCmd.PersistentFlags().Bool(config.FlagPrintConfigOnly, false, "Prints the configuration only and exists. "+
|
||||||
_ = viper.BindPFlag(configRunOnStart, doCmd.PersistentFlags().Lookup("runOnStart"))
|
"Can be used to debug the config E.g: when having authentication issues.")
|
||||||
doCmd.PersistentFlags().Int("api-port", 8080, "Sync API Port, the API endpoint will be started to enable remote triggering; if 0 port API is disabled.")
|
doCmd.PersistentFlags().Bool(config.FlagContinueOnError, false, "If enabled, the synchronization task "+
|
||||||
_ = viper.BindPFlag(configAPIPort, doCmd.PersistentFlags().Lookup("api-port"))
|
"will not fail on single errors, but will log the errors and continue.")
|
||||||
doCmd.PersistentFlags().String("api-username", "", "Sync API username")
|
|
||||||
_ = viper.BindPFlag(configAPIUsername, doCmd.PersistentFlags().Lookup("api-username"))
|
|
||||||
doCmd.PersistentFlags().String("api-password", "", "Sync API password")
|
|
||||||
_ = viper.BindPFlag(configAPIPassword, doCmd.PersistentFlags().Lookup("api-password"))
|
|
||||||
|
|
||||||
doCmd.PersistentFlags().String("beta", "", "Enable beta features (comma separated list)")
|
doCmd.PersistentFlags().
|
||||||
_ = viper.BindPFlag(configBeta, doCmd.PersistentFlags().Lookup("beta"))
|
Int(config.FlagAPIPort, 8080, "Sync API Port, the API endpoint will be started to enable remote triggering; if 0 port API is disabled.")
|
||||||
|
doCmd.PersistentFlags().String(config.FlagAPIUsername, "", "Sync API username")
|
||||||
|
doCmd.PersistentFlags().String(config.FlagAPIPassword, "", "Sync API password")
|
||||||
|
doCmd.PersistentFlags().String(config.FlagAPIDarkMode, "", "API UI in dark mode")
|
||||||
|
|
||||||
doCmd.PersistentFlags().String("origin-url", "", "Origin instance url")
|
doCmd.PersistentFlags().Bool(config.FlagFeatureDhcpServerConfig, true, "Enable DHCP server config feature")
|
||||||
_ = viper.BindPFlag(configOriginURL, doCmd.PersistentFlags().Lookup("origin-url"))
|
doCmd.PersistentFlags().Bool(config.FlagFeatureDhcpStaticLeases, true, "Enable DHCP server static leases feature")
|
||||||
doCmd.PersistentFlags().String("origin-api-path", "/control", "Origin instance API path")
|
|
||||||
_ = viper.BindPFlag(configOriginAPIPath, doCmd.PersistentFlags().Lookup("origin-api-path"))
|
|
||||||
doCmd.PersistentFlags().String("origin-username", "", "Origin instance username")
|
|
||||||
_ = viper.BindPFlag(configOriginUsername, doCmd.PersistentFlags().Lookup("origin-username"))
|
|
||||||
doCmd.PersistentFlags().String("origin-password", "", "Origin instance password")
|
|
||||||
_ = viper.BindPFlag(configOriginPassword, doCmd.PersistentFlags().Lookup("origin-password"))
|
|
||||||
doCmd.PersistentFlags().String("origin-insecure-skip-verify", "", "Enable Origin instance InsecureSkipVerify")
|
|
||||||
_ = viper.BindPFlag(configOriginInsecureSkipVerify, doCmd.PersistentFlags().Lookup("origin-insecure-skip-verify"))
|
|
||||||
|
|
||||||
doCmd.PersistentFlags().String("replica-url", "", "Replica instance url")
|
doCmd.PersistentFlags().Bool(config.FlagFeatureDNSServerConfig, true, "Enable DNS server config feature")
|
||||||
_ = viper.BindPFlag(configReplicaURL, doCmd.PersistentFlags().Lookup("replica-url"))
|
doCmd.PersistentFlags().Bool(config.FlagFeatureDNSAccessLists, true, "Enable DNS server access lists feature")
|
||||||
doCmd.PersistentFlags().String("replica-api-path", "/control", "Replica instance API path")
|
doCmd.PersistentFlags().Bool(config.FlagFeatureDNSRewrites, true, "Enable DNS rewrites feature")
|
||||||
_ = viper.BindPFlag(configReplicaAPIPath, doCmd.PersistentFlags().Lookup("replica-api-path"))
|
|
||||||
doCmd.PersistentFlags().String("replica-username", "", "Replica instance username")
|
doCmd.PersistentFlags().Bool(config.FlagFeatureGeneral, true, "Enable general settings feature")
|
||||||
_ = viper.BindPFlag(configReplicaUsername, doCmd.PersistentFlags().Lookup("replica-username"))
|
doCmd.PersistentFlags().Bool(config.FlagFeatureQueryLog, true, "Enable query log config feature")
|
||||||
doCmd.PersistentFlags().String("replica-password", "", "Replica instance password")
|
doCmd.PersistentFlags().Bool(config.FlagFeatureStats, true, "Enable stats config feature")
|
||||||
_ = viper.BindPFlag(configReplicaPassword, doCmd.PersistentFlags().Lookup("replica-password"))
|
doCmd.PersistentFlags().Bool(config.FlagFeatureClient, true, "Enable client settings feature")
|
||||||
doCmd.PersistentFlags().Bool("replica-insecure-skip-verify", false, "Enable Replica instance InsecureSkipVerify")
|
doCmd.PersistentFlags().Bool(config.FlagFeatureServices, true, "Enable services sync feature")
|
||||||
_ = viper.BindPFlag(configReplicaInsecureSkipVerify, doCmd.PersistentFlags().Lookup("replica-insecure-skip-verify"))
|
doCmd.PersistentFlags().Bool(config.FlagFeatureFilters, true, "Enable filters sync feature")
|
||||||
doCmd.PersistentFlags().Bool("replica-auto-setup", false, "Enable automatic setup of new AdguardHome instances. This replaces the setup wizard.")
|
doCmd.PersistentFlags().Bool(config.FlagFeatureTLSConfig, false, "Enable TLS config sync feature")
|
||||||
_ = viper.BindPFlag(configReplicaAutoSetup, doCmd.PersistentFlags().Lookup("replica-auto-setup"))
|
|
||||||
|
doCmd.PersistentFlags().String(config.FlagOriginURL, "", "Origin instance url")
|
||||||
|
doCmd.PersistentFlags().
|
||||||
|
String(config.FlagOriginWebURL, "", "Origin instance web url used in the web interface (default: <origin-url>)")
|
||||||
|
doCmd.PersistentFlags().String(config.FlagOriginAPIPath, "/control", "Origin instance API path")
|
||||||
|
doCmd.PersistentFlags().String(config.FlagOriginUsername, "", "Origin instance username")
|
||||||
|
doCmd.PersistentFlags().String(config.FlagOriginPassword, "", "Origin instance password")
|
||||||
|
doCmd.PersistentFlags().String(config.FlagOriginCookie, "", "If Set, uses a cookie for authentication")
|
||||||
|
doCmd.PersistentFlags().Bool(config.FlagOriginISV, false, "Enable Origin instance InsecureSkipVerify")
|
||||||
|
|
||||||
|
doCmd.PersistentFlags().String(config.FlagReplicaURL, "", "Replica instance url")
|
||||||
|
doCmd.PersistentFlags().
|
||||||
|
String(config.FlagReplicaWebURL, "", "Replica instance web url used in the web interface (default: <replica-url>)")
|
||||||
|
doCmd.PersistentFlags().String(config.FlagReplicaAPIPath, "/control", "Replica instance API path")
|
||||||
|
doCmd.PersistentFlags().String(config.FlagReplicaUsername, "", "Replica instance username")
|
||||||
|
doCmd.PersistentFlags().String(config.FlagReplicaPassword, "", "Replica instance password")
|
||||||
|
doCmd.PersistentFlags().String(config.FlagReplicaCookie, "", "If Set, uses a cookie for authentication")
|
||||||
|
doCmd.PersistentFlags().Bool(config.FlagReplicaISV, false, "Enable Replica instance InsecureSkipVerify")
|
||||||
|
doCmd.PersistentFlags().
|
||||||
|
Bool(config.FlagReplicaAutoSetup, false, "Enable automatic setup of new AdguardHome instances. This replaces the setup wizard.")
|
||||||
|
doCmd.PersistentFlags().
|
||||||
|
String(config.FlagReplicaInterfaceName, "", "Optional change the interface name of the replica if it differs from the master")
|
||||||
}
|
}
|
||||||
|
|||||||
83
go.mod
83
go.mod
@@ -1,16 +1,79 @@
|
|||||||
module github.com/bakito/adguardhome-sync
|
module github.com/bakito/adguardhome-sync
|
||||||
|
|
||||||
go 1.16
|
go 1.25.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-resty/resty/v2 v2.6.0
|
github.com/caarlos0/env/v11 v11.3.1
|
||||||
github.com/golang/mock v1.6.0
|
github.com/gin-gonic/gin v1.10.1
|
||||||
github.com/google/uuid v1.3.0
|
github.com/go-faker/faker/v4 v4.6.2
|
||||||
github.com/mitchellh/go-homedir v1.1.0
|
github.com/go-resty/resty/v2 v2.16.5
|
||||||
github.com/onsi/ginkgo v1.16.4
|
github.com/google/uuid v1.6.0
|
||||||
github.com/onsi/gomega v1.15.0
|
github.com/jinzhu/copier v0.4.0
|
||||||
|
github.com/oapi-codegen/runtime v1.1.2
|
||||||
|
github.com/onsi/ginkgo/v2 v2.25.3
|
||||||
|
github.com/onsi/gomega v1.38.2
|
||||||
|
github.com/prometheus/client_golang v1.23.2
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/spf13/cobra v1.2.1
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
|
||||||
github.com/spf13/viper v1.8.1
|
github.com/spf13/cobra v1.10.1
|
||||||
go.uber.org/zap v1.19.0
|
go.uber.org/mock v0.6.0
|
||||||
|
go.uber.org/zap v1.27.0
|
||||||
|
golang.org/x/mod v0.28.0
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
k8s.io/apimachinery v0.34.1
|
||||||
|
k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
||||||
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/bytedance/sonic v1.13.2 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||||
|
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||||
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
||||||
|
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
|
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
|
github.com/prometheus/common v0.66.1 // indirect
|
||||||
|
github.com/prometheus/procfs v0.16.1 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.9 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
|
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||||
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
|
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
|
golang.org/x/arch v0.16.0 // indirect
|
||||||
|
golang.org/x/crypto v0.41.0 // indirect
|
||||||
|
golang.org/x/net v0.43.0 // indirect
|
||||||
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
|
golang.org/x/text v0.29.0 // indirect
|
||||||
|
golang.org/x/tools v0.36.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.8 // indirect
|
||||||
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
|
k8s.io/klog/v2 v2.130.1 // indirect
|
||||||
|
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
|
||||||
|
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||||
|
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
783
go.sum
783
go.sum
@@ -1,646 +1,225 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
||||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
||||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
||||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
||||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
|
||||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
|
||||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
|
||||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
|
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
|
||||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
|
||||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
|
||||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
|
||||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
|
||||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
|
||||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
|
||||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
|
||||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
|
||||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
|
||||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
|
||||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
|
||||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
|
||||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
|
||||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
|
||||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
|
||||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
|
||||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
|
||||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
|
||||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
|
||||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
|
||||||
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
|
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
|
||||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
|
||||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/go-faker/faker/v4 v4.6.2 h1:IR1uQUYotFZnuTL7Iuy0FDGtHM5Rt1Q+2nipH9gnqKs=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/go-faker/faker/v4 v4.6.2/go.mod h1:u1dIRP5neLB6kTzgyVjdBOV5R1uP7BdxkcWk7tiKQXk=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-resty/resty/v2 v2.6.0 h1:joIR5PNLM2EFqqESUjCMGXrWmXNHEU9CEiK813oKYS4=
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||||
|
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||||
|
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
|
||||||
|
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
|
||||||
|
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||||
|
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||||
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
|
||||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
|
||||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
|
||||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
|
||||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
|
||||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
|
||||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
|
||||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
|
||||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
|
||||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
|
||||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
|
||||||
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
|
||||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY=
|
||||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
|
||||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
||||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
|
||||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
|
||||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
|
||||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
|
||||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
|
||||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
|
||||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
|
||||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
|
||||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
|
||||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
|
||||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
|
||||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
|
||||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
|
||||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
|
||||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
|
||||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
|
||||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
|
||||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
|
||||||
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
|
||||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
|
||||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI=
|
||||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
github.com/onsi/ginkgo/v2 v2.25.3 h1:Ty8+Yi/ayDAGtk4XxmmfUy4GabvM+MegeB4cDLRi6nw=
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
github.com/onsi/ginkgo/v2 v2.25.3/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE=
|
||||||
github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU=
|
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
|
||||||
github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
|
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ=
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
|
||||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||||
|
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||||
|
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||||
|
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||||
|
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||||
|
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||||
|
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||||
|
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||||
|
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
||||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
||||||
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
|
|
||||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
|
||||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
|
||||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
|
||||||
github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
|
|
||||||
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
|
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
|
||||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
|
||||||
github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44=
|
|
||||||
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
|
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||||
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
golang.org/x/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U=
|
||||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
golang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
|
||||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
|
||||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
|
||||||
go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE=
|
|
||||||
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
|
|
||||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
|
||||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
|
||||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
|
||||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
|
||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
|
||||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
|
|
||||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
|
||||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
|
||||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
|
||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
|
|
||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
|
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
|
||||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
|
||||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
|
||||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
|
||||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
|
||||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
|
||||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
|
||||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||||
golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA=
|
|
||||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
|
||||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
|
||||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
|
||||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
|
||||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
|
||||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
|
||||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
|
||||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
|
||||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
|
||||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
|
||||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
|
||||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
|
||||||
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
|
|
||||||
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
|
|
||||||
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
|
||||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
|
||||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
|
||||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
|
||||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
|
||||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
|
||||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
|
||||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
|
||||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
|
||||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
|
||||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
|
||||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
|
||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
|
||||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
|
||||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
|
||||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
|
||||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
|
||||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
|
||||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
|
||||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
|
|
||||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
|
||||||
|
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||||
|
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
|
||||||
|
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
|
||||||
|
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
||||||
|
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
||||||
|
|||||||
105
internal/client/client-methods.go
Normal file
105
internal/client/client-methods.go
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (cl *client) doGet(req *resty.Request, url string) error {
|
||||||
|
rl := cl.log.With("method", "GET", "path", url)
|
||||||
|
if cl.client.UserInfo != nil {
|
||||||
|
rl = rl.With("username", cl.client.UserInfo.Username)
|
||||||
|
}
|
||||||
|
req.ForceContentType("application/json")
|
||||||
|
rl.Debug("do get")
|
||||||
|
resp, err := req.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
l := rl
|
||||||
|
if resp != nil {
|
||||||
|
if resp.StatusCode() == http.StatusFound {
|
||||||
|
loc := resp.Header().Get("Location")
|
||||||
|
if loc == "/install.html" || loc == "/control/install.html" {
|
||||||
|
return ErrSetupNeeded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l = l.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Debug("error in do get")
|
||||||
|
return detailedError(resp, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAuthenticationIssue(resp, rl)
|
||||||
|
|
||||||
|
rl.With(
|
||||||
|
"status", resp.StatusCode(),
|
||||||
|
"body", string(resp.Body()),
|
||||||
|
"content-type", resp.Header()["Content-Type"],
|
||||||
|
).Debug("got response")
|
||||||
|
if resp.StatusCode() != http.StatusOK {
|
||||||
|
return detailedError(resp, nil)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) doPost(req *resty.Request, url string) error {
|
||||||
|
rl := cl.log.With("method", "POST", "path", url)
|
||||||
|
if cl.client.UserInfo != nil {
|
||||||
|
rl = rl.With("username", cl.client.UserInfo.Username)
|
||||||
|
}
|
||||||
|
b, _ := json.Marshal(req.Body)
|
||||||
|
rl.With("body", string(b)).Debug("do post")
|
||||||
|
resp, err := req.Post(url)
|
||||||
|
if err != nil {
|
||||||
|
rl.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err).Debug("error in do post")
|
||||||
|
return detailedError(resp, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAuthenticationIssue(resp, rl)
|
||||||
|
|
||||||
|
rl.With(
|
||||||
|
"status", resp.StatusCode(),
|
||||||
|
"body", string(resp.Body()),
|
||||||
|
"content-type", contentType(resp),
|
||||||
|
).Debug("got response")
|
||||||
|
if resp.StatusCode() != http.StatusOK {
|
||||||
|
return detailedError(resp, nil)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) doPut(req *resty.Request, url string) error {
|
||||||
|
rl := cl.log.With("method", "PUT", "path", url)
|
||||||
|
if cl.client.UserInfo != nil {
|
||||||
|
rl = rl.With("username", cl.client.UserInfo.Username)
|
||||||
|
}
|
||||||
|
b, _ := json.Marshal(req.Body)
|
||||||
|
rl.With("body", string(b)).Debug("do put")
|
||||||
|
resp, err := req.Put(url)
|
||||||
|
if err != nil {
|
||||||
|
rl.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err).Debug("error in do put")
|
||||||
|
return detailedError(resp, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAuthenticationIssue(resp, rl)
|
||||||
|
|
||||||
|
rl.With(
|
||||||
|
"status", resp.StatusCode(),
|
||||||
|
"body", string(resp.Body()),
|
||||||
|
"content-type", contentType(resp),
|
||||||
|
).Debug("got response")
|
||||||
|
if resp.StatusCode() != http.StatusOK {
|
||||||
|
return detailedError(resp, nil)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkAuthenticationIssue(resp *resty.Response, rl *zap.SugaredLogger) {
|
||||||
|
if resp != nil && (resp.StatusCode() == http.StatusUnauthorized || resp.StatusCode() == http.StatusForbidden) {
|
||||||
|
rl.With("status", resp.StatusCode()).Error("there seems to be an authentication issue - " +
|
||||||
|
"please check https://github.com/bakito/adguardhome-sync/wiki/FAQ")
|
||||||
|
}
|
||||||
|
}
|
||||||
481
internal/client/client.go
Normal file
481
internal/client/client.go
Normal file
@@ -0,0 +1,481 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/log"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
const envRedirectPolicyNoOfRedirects = "REDIRECT_POLICY_NO_OF_REDIRECTS"
|
||||||
|
|
||||||
|
type Error struct {
|
||||||
|
message string
|
||||||
|
errorCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
return e.message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Code() int {
|
||||||
|
return e.errorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
l = log.GetLogger("client")
|
||||||
|
// ErrSetupNeeded custom error.
|
||||||
|
ErrSetupNeeded = errors.New("setup needed")
|
||||||
|
)
|
||||||
|
|
||||||
|
func detailedError(resp *resty.Response, err error) error {
|
||||||
|
e := resp.Status()
|
||||||
|
if len(resp.Body()) > 0 {
|
||||||
|
e += fmt.Sprintf("(%s)", string(resp.Body()))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
e += ": " + err.Error()
|
||||||
|
}
|
||||||
|
return &Error{
|
||||||
|
message: e,
|
||||||
|
errorCode: resp.StatusCode(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New create a new client.
|
||||||
|
func New(config types.AdGuardInstance) (Client, error) {
|
||||||
|
var apiURL string
|
||||||
|
if config.APIPath == "" {
|
||||||
|
apiURL = config.URL + "/control"
|
||||||
|
} else {
|
||||||
|
apiURL = fmt.Sprintf("%s/%s", config.URL, config.APIPath)
|
||||||
|
}
|
||||||
|
u, err := url.Parse(apiURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
u.Path = path.Clean(u.Path)
|
||||||
|
cl := resty.New().SetBaseURL(u.String()).SetDisableWarn(true).SetHeaders(config.RequestHeaders)
|
||||||
|
|
||||||
|
// #nosec G402 has to be explicitly enabled
|
||||||
|
cl.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: config.InsecureSkipVerify})
|
||||||
|
|
||||||
|
cookieParts := strings.Split(config.Cookie, "=")
|
||||||
|
if len(cookieParts) == 2 {
|
||||||
|
cl.SetCookie(&http.Cookie{
|
||||||
|
Name: cookieParts[0],
|
||||||
|
Value: cookieParts[1],
|
||||||
|
})
|
||||||
|
} else if config.Username != "" && config.Password != "" {
|
||||||
|
cl = cl.SetBasicAuth(config.Username, config.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := os.LookupEnv(envRedirectPolicyNoOfRedirects); ok {
|
||||||
|
nbr, err := strconv.Atoi(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing env var %q value must be an integer", envRedirectPolicyNoOfRedirects)
|
||||||
|
}
|
||||||
|
cl.SetRedirectPolicy(resty.FlexibleRedirectPolicy(nbr))
|
||||||
|
} else {
|
||||||
|
// no redirect
|
||||||
|
cl.SetRedirectPolicy(resty.NoRedirectPolicy())
|
||||||
|
}
|
||||||
|
|
||||||
|
return &client{
|
||||||
|
host: config.Host,
|
||||||
|
client: cl,
|
||||||
|
log: l.With("host", config.Host),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client AdguardHome API client interface.
|
||||||
|
//
|
||||||
|
//nolint:interfacebloat
|
||||||
|
type Client interface {
|
||||||
|
Host() string
|
||||||
|
Status() (*model.ServerStatus, error)
|
||||||
|
Stats() (*model.Stats, error)
|
||||||
|
QueryLog(limit int) (*model.QueryLog, error)
|
||||||
|
ToggleProtection(enable bool) error
|
||||||
|
RewriteList() (*model.RewriteEntries, error)
|
||||||
|
AddRewriteEntries(e ...model.RewriteEntry) error
|
||||||
|
DeleteRewriteEntries(e ...model.RewriteEntry) error
|
||||||
|
Filtering() (*model.FilterStatus, error)
|
||||||
|
ToggleFiltering(enabled bool, interval int) error
|
||||||
|
AddFilter(whitelist bool, f model.Filter) error
|
||||||
|
DeleteFilter(whitelist bool, f model.Filter) error
|
||||||
|
UpdateFilter(whitelist bool, f model.Filter) error
|
||||||
|
RefreshFilters(whitelist bool) error
|
||||||
|
SetCustomRules(rules *[]string) error
|
||||||
|
SafeBrowsing() (bool, error)
|
||||||
|
ToggleSafeBrowsing(enable bool) error
|
||||||
|
Parental() (bool, error)
|
||||||
|
ToggleParental(enable bool) error
|
||||||
|
SafeSearchConfig() (*model.SafeSearchConfig, error)
|
||||||
|
SetSafeSearchConfig(settings *model.SafeSearchConfig) error
|
||||||
|
ProfileInfo() (*model.ProfileInfo, error)
|
||||||
|
SetProfileInfo(settings *model.ProfileInfo) error
|
||||||
|
BlockedServicesSchedule() (*model.BlockedServicesSchedule, error)
|
||||||
|
SetBlockedServicesSchedule(schedule *model.BlockedServicesSchedule) error
|
||||||
|
Clients() (*model.Clients, error)
|
||||||
|
AddClient(client *model.Client) error
|
||||||
|
UpdateClient(client *model.Client) error
|
||||||
|
DeleteClient(client *model.Client) error
|
||||||
|
QueryLogConfig() (*model.QueryLogConfigWithIgnored, error)
|
||||||
|
SetQueryLogConfig(ql *model.QueryLogConfigWithIgnored) error
|
||||||
|
StatsConfig() (*model.GetStatsConfigResponse, error)
|
||||||
|
SetStatsConfig(sc *model.PutStatsConfigUpdateRequest) error
|
||||||
|
Setup() error
|
||||||
|
AccessList() (*model.AccessList, error)
|
||||||
|
SetAccessList(accessList *model.AccessList) error
|
||||||
|
DNSConfig() (*model.DNSConfig, error)
|
||||||
|
SetDNSConfig(config *model.DNSConfig) error
|
||||||
|
DhcpConfig() (*model.DhcpStatus, error)
|
||||||
|
SetDhcpConfig(status *model.DhcpStatus) error
|
||||||
|
AddDHCPStaticLease(lease model.DhcpStaticLease) error
|
||||||
|
DeleteDHCPStaticLease(lease model.DhcpStaticLease) error
|
||||||
|
TLSConfig() (*model.TlsConfig, error)
|
||||||
|
SetTLSConfig(tls *model.TlsConfig) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type client struct {
|
||||||
|
client *resty.Client
|
||||||
|
log *zap.SugaredLogger
|
||||||
|
host string
|
||||||
|
version string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) Host() string {
|
||||||
|
return cl.host
|
||||||
|
}
|
||||||
|
|
||||||
|
func contentType(resp *resty.Response) string {
|
||||||
|
if ct, ok := resp.Header()["Content-Type"]; ok {
|
||||||
|
if len(ct) != 1 {
|
||||||
|
return fmt.Sprintf("%v", ct)
|
||||||
|
}
|
||||||
|
return ct[0]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) Status() (*model.ServerStatus, error) {
|
||||||
|
status := &model.ServerStatus{}
|
||||||
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(status), "status")
|
||||||
|
cl.version = status.Version
|
||||||
|
return status, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) Stats() (*model.Stats, error) {
|
||||||
|
stats := &model.Stats{}
|
||||||
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(stats), "stats")
|
||||||
|
return stats, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) QueryLog(limit int) (*model.QueryLog, error) {
|
||||||
|
ql := &model.QueryLog{}
|
||||||
|
err := cl.doGet(
|
||||||
|
cl.client.R().EnableTrace().SetResult(ql),
|
||||||
|
fmt.Sprintf(`querylog?limit=%d&response_status="all"`, limit),
|
||||||
|
)
|
||||||
|
return ql, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) RewriteList() (*model.RewriteEntries, error) {
|
||||||
|
rewrites := &model.RewriteEntries{}
|
||||||
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(&rewrites), "/rewrite/list")
|
||||||
|
return rewrites, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) AddRewriteEntries(entries ...model.RewriteEntry) error {
|
||||||
|
for _, e := range entries {
|
||||||
|
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Add DNS rewrite entry")
|
||||||
|
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&e), "/rewrite/add")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) DeleteRewriteEntries(entries ...model.RewriteEntry) error {
|
||||||
|
for _, e := range entries {
|
||||||
|
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Delete DNS rewrite entry")
|
||||||
|
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&e), "/rewrite/delete")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) SafeBrowsing() (bool, error) {
|
||||||
|
return cl.toggleStatus("safebrowsing")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) ToggleSafeBrowsing(enable bool) error {
|
||||||
|
return cl.toggleBool("safebrowsing", enable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) Parental() (bool, error) {
|
||||||
|
return cl.toggleStatus("parental")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) ToggleParental(enable bool) error {
|
||||||
|
return cl.toggleBool("parental", enable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) toggleStatus(mode string) (bool, error) {
|
||||||
|
fs := &model.EnableConfig{}
|
||||||
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(fs), fmt.Sprintf("/%s/status", mode))
|
||||||
|
return fs.Enabled, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) toggleBool(mode string, enable bool) error {
|
||||||
|
cl.log.With("enable", enable).Info("Toggle " + mode)
|
||||||
|
var target string
|
||||||
|
if enable {
|
||||||
|
target = "enable"
|
||||||
|
} else {
|
||||||
|
target = "disable"
|
||||||
|
}
|
||||||
|
return cl.doPost(cl.client.R().EnableTrace(), fmt.Sprintf("/%s/%s", mode, target))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) Filtering() (*model.FilterStatus, error) {
|
||||||
|
f := &model.FilterStatus{}
|
||||||
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(f), "/filtering/status")
|
||||||
|
return f, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) AddFilter(whitelist bool, f model.Filter) error {
|
||||||
|
cl.log.With("url", f.Url, "whitelist", whitelist, "enabled", f.Enabled).Info("Add filter")
|
||||||
|
ff := &model.AddUrlRequest{Name: utils.Ptr(f.Name), Url: utils.Ptr(f.Url), Whitelist: utils.Ptr(whitelist)}
|
||||||
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(ff), "/filtering/add_url")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) DeleteFilter(whitelist bool, f model.Filter) error {
|
||||||
|
cl.log.With("url", f.Url, "whitelist", whitelist, "enabled", f.Enabled).Info("Delete filter")
|
||||||
|
ff := &model.RemoveUrlRequest{Url: utils.Ptr(f.Url), Whitelist: utils.Ptr(whitelist)}
|
||||||
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(ff), "/filtering/remove_url")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) UpdateFilter(whitelist bool, f model.Filter) error {
|
||||||
|
cl.log.With("url", f.Url, "whitelist", whitelist, "enabled", f.Enabled).Info("Update filter")
|
||||||
|
fu := &model.FilterSetUrl{
|
||||||
|
Whitelist: utils.Ptr(whitelist), Url: utils.Ptr(f.Url),
|
||||||
|
Data: &model.FilterSetUrlData{Name: f.Name, Url: f.Url, Enabled: f.Enabled},
|
||||||
|
}
|
||||||
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(fu), "/filtering/set_url")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) RefreshFilters(whitelist bool) error {
|
||||||
|
cl.log.With("whitelist", whitelist).Info("Refresh filter")
|
||||||
|
return cl.doPost(
|
||||||
|
cl.client.R().EnableTrace().SetBody(&model.FilterRefreshRequest{Whitelist: utils.Ptr(whitelist)}),
|
||||||
|
"/filtering/refresh",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) ToggleProtection(enable bool) error {
|
||||||
|
cl.log.With("enable", enable).Info("Toggle protection")
|
||||||
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.Protection{ProtectionEnabled: enable}), "/dns_config")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) SetCustomRules(rules *[]string) error {
|
||||||
|
var l int
|
||||||
|
if rules != nil {
|
||||||
|
l = len(*rules)
|
||||||
|
}
|
||||||
|
cl.log.With("rules", l).Info("Set user rules")
|
||||||
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(&model.SetRulesRequest{Rules: rules}), "/filtering/set_rules")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) ToggleFiltering(enabled bool, interval int) error {
|
||||||
|
cl.log.With("enabled", enabled, "interval", interval).Info("Toggle filtering")
|
||||||
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(&model.FilterConfig{
|
||||||
|
Enabled: utils.Ptr(enabled),
|
||||||
|
Interval: utils.Ptr(interval),
|
||||||
|
}), "/filtering/config")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) BlockedServicesSchedule() (*model.BlockedServicesSchedule, error) {
|
||||||
|
sched := &model.BlockedServicesSchedule{}
|
||||||
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(sched), "/blocked_services/get")
|
||||||
|
return sched, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) SetBlockedServicesSchedule(schedule *model.BlockedServicesSchedule) error {
|
||||||
|
cl.log.With("services", schedule.ServicesString()).Info("Set blocked services schedule")
|
||||||
|
return cl.doPut(cl.client.R().EnableTrace().SetBody(schedule), "/blocked_services/update")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) Clients() (*model.Clients, error) {
|
||||||
|
clients := &model.Clients{}
|
||||||
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(clients), "/clients")
|
||||||
|
return clients, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) AddClient(client *model.Client) error {
|
||||||
|
cl.log.With("name", *client.Name).Info("Add client settings")
|
||||||
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(client), "/clients/add")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) UpdateClient(client *model.Client) error {
|
||||||
|
cl.log.With("name", *client.Name).Info("Update client settings")
|
||||||
|
return cl.doPost(
|
||||||
|
cl.client.R().EnableTrace().SetBody(&model.ClientUpdate{Name: client.Name, Data: client}),
|
||||||
|
"/clients/update",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) DeleteClient(client *model.Client) error {
|
||||||
|
cl.log.With("name", *client.Name).Info("Delete client settings")
|
||||||
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(client), "/clients/delete")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) QueryLogConfig() (*model.QueryLogConfigWithIgnored, error) {
|
||||||
|
qlc := &model.QueryLogConfigWithIgnored{}
|
||||||
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(qlc), "/querylog/config")
|
||||||
|
return qlc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) SetQueryLogConfig(qlc *model.QueryLogConfigWithIgnored) error {
|
||||||
|
cl.log.With("enabled", *qlc.Enabled, "interval", *qlc.Interval, "anonymizeClientIP", *qlc.AnonymizeClientIp).
|
||||||
|
Info("Set query log config")
|
||||||
|
return cl.doPut(cl.client.R().EnableTrace().SetBody(qlc), "/querylog/config/update")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) StatsConfig() (*model.GetStatsConfigResponse, error) {
|
||||||
|
stats := &model.GetStatsConfigResponse{}
|
||||||
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(stats), "/stats/config")
|
||||||
|
return stats, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) SetStatsConfig(sc *model.PutStatsConfigUpdateRequest) error {
|
||||||
|
cl.log.With("interval", sc.Interval).Info("Set stats config")
|
||||||
|
return cl.doPut(cl.client.R().EnableTrace().SetBody(sc), "/stats/config/update")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) Setup() error {
|
||||||
|
cl.log.Info("Setup new AdguardHome instance")
|
||||||
|
cfg := &types.InstallConfig{
|
||||||
|
Web: types.InstallPort{
|
||||||
|
IP: "0.0.0.0",
|
||||||
|
Port: 3000,
|
||||||
|
Status: "",
|
||||||
|
CanAutofix: false,
|
||||||
|
},
|
||||||
|
DNS: types.InstallPort{
|
||||||
|
IP: "0.0.0.0",
|
||||||
|
Port: 53,
|
||||||
|
Status: "",
|
||||||
|
CanAutofix: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if cl.client.UserInfo != nil {
|
||||||
|
cfg.Username = cl.client.UserInfo.Username
|
||||||
|
cfg.Password = cl.client.UserInfo.Password
|
||||||
|
}
|
||||||
|
req := cl.client.R().EnableTrace().SetBody(cfg)
|
||||||
|
req.UserInfo = nil
|
||||||
|
return cl.doPost(req, "/install/configure")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) AccessList() (*model.AccessList, error) {
|
||||||
|
al := &model.AccessList{}
|
||||||
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(al), "/access/list")
|
||||||
|
return al, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) SetAccessList(list *model.AccessList) error {
|
||||||
|
cl.log.Info("Set access list")
|
||||||
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(list), "/access/set")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) DNSConfig() (*model.DNSConfig, error) {
|
||||||
|
cfg := &model.DNSConfig{}
|
||||||
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(cfg), "/dns_info")
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) SetDNSConfig(config *model.DNSConfig) error {
|
||||||
|
cl.log.Info("Set dns config list")
|
||||||
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(config), "/dns_config")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) DhcpConfig() (*model.DhcpStatus, error) {
|
||||||
|
cfg := &model.DhcpStatus{}
|
||||||
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(cfg), "/dhcp/status")
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) SetDhcpConfig(config *model.DhcpStatus) error {
|
||||||
|
cl.log.Info("Set dhcp server config")
|
||||||
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(config), "/dhcp/set_config")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) AddDHCPStaticLease(l model.DhcpStaticLease) error {
|
||||||
|
cl.log.With("mac", l.Mac, "ip", l.Ip, "hostname", l.Hostname).Info("Add static dhcp lease")
|
||||||
|
err := cl.doPost(cl.client.R().EnableTrace().SetBody(l), "/dhcp/add_static_lease")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) DeleteDHCPStaticLease(l model.DhcpStaticLease) error {
|
||||||
|
cl.log.With("mac", l.Mac, "ip", l.Ip, "hostname", l.Hostname).Info("Delete static dhcp lease")
|
||||||
|
err := cl.doPost(cl.client.R().EnableTrace().SetBody(l), "/dhcp/remove_static_lease")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) SafeSearchConfig() (*model.SafeSearchConfig, error) {
|
||||||
|
sss := &model.SafeSearchConfig{}
|
||||||
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(sss), "/safesearch/status")
|
||||||
|
return sss, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) SetSafeSearchConfig(settings *model.SafeSearchConfig) error {
|
||||||
|
cl.log.With("enabled", *settings.Enabled).Info("Set safesearch settings")
|
||||||
|
return cl.doPut(cl.client.R().EnableTrace().SetBody(settings), "/safesearch/settings")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) ProfileInfo() (*model.ProfileInfo, error) {
|
||||||
|
p := &model.ProfileInfo{}
|
||||||
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(p), "/profile")
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) SetProfileInfo(profile *model.ProfileInfo) error {
|
||||||
|
cl.log.With("language", profile.Language, "theme", profile.Theme).Info("Set profile")
|
||||||
|
return cl.doPut(cl.client.R().EnableTrace().SetBody(profile), "/profile/update")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) TLSConfig() (*model.TlsConfig, error) {
|
||||||
|
tlsc := &model.TlsConfig{}
|
||||||
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(tlsc), "/tls/status")
|
||||||
|
return tlsc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) SetTLSConfig(tlsc *model.TlsConfig) error {
|
||||||
|
cl.log.With("enabled", tlsc.Enabled).Info("Set TLS config")
|
||||||
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(tlsc), "/tls/configure")
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ package client_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -2,17 +2,20 @@ package client_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
"github.com/google/uuid"
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/client"
|
"github.com/bakito/adguardhome-sync/internal/client"
|
||||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
"github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
"github.com/google/uuid"
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -21,7 +24,6 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Client", func() {
|
var _ = Describe("Client", func() {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cl client.Client
|
cl client.Client
|
||||||
ts *httptest.Server
|
ts *httptest.Server
|
||||||
@@ -34,19 +36,22 @@ var _ = Describe("Client", func() {
|
|||||||
|
|
||||||
Context("Host", func() {
|
Context("Host", func() {
|
||||||
It("should read the current host", func() {
|
It("should read the current host", func() {
|
||||||
cl, _ := client.New(types.AdGuardInstance{URL: "https://foo.bar:3000"})
|
inst := types.AdGuardInstance{URL: "https://foo.bar:3000"}
|
||||||
|
err := inst.Init()
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
cl, _ := client.New(inst)
|
||||||
host := cl.Host()
|
host := cl.Host()
|
||||||
Ω(host).Should(Equal("foo.bar:3000"))
|
Ω(host).Should(Equal("foo.bar:3000"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Context("Filtering", func() {
|
Context("Filter", func() {
|
||||||
It("should read filtering status", func() {
|
It("should read filter status", func() {
|
||||||
ts, cl = ClientGet("filtering-status.json", "/filtering/status")
|
ts, cl = ClientGet("filtering-status.json", "/filtering/status")
|
||||||
fs, err := cl.Filtering()
|
fs, err := cl.Filtering()
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(fs.Enabled).Should(BeTrue())
|
Ω(*fs.Enabled).Should(BeTrue())
|
||||||
Ω(fs.Filters).Should(HaveLen(2))
|
Ω(*fs.Filters).Should(HaveLen(2))
|
||||||
})
|
})
|
||||||
It("should enable protection", func() {
|
It("should enable protection", func() {
|
||||||
ts, cl = ClientPost("/filtering/config", `{"enabled":true,"interval":123}`)
|
ts, cl = ClientPost("/filtering/config", `{"enabled":true,"interval":123}`)
|
||||||
@@ -65,35 +70,46 @@ var _ = Describe("Client", func() {
|
|||||||
})
|
})
|
||||||
It("should add Filters", func() {
|
It("should add Filters", func() {
|
||||||
ts, cl = ClientPost("/filtering/add_url",
|
ts, cl = ClientPost("/filtering/add_url",
|
||||||
`{"id":0,"enabled":false,"url":"foo","name":"","rules_count":0,"whitelist":true}`,
|
`{"name":"","url":"foo","whitelist":true}`,
|
||||||
`{"id":0,"enabled":false,"url":"bar","name":"","rules_count":0,"whitelist":true}`,
|
`{"name":"","url":"bar","whitelist":true}`,
|
||||||
)
|
)
|
||||||
err := cl.AddFilters(true, types.Filter{URL: "foo"}, types.Filter{URL: "bar"})
|
err := cl.AddFilter(true, model.Filter{Url: "foo"})
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
err = cl.AddFilter(true, model.Filter{Url: "bar"})
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should update Filters", func() {
|
It("should update Filters", func() {
|
||||||
ts, cl = ClientPost("/filtering/set_url",
|
ts, cl = ClientPost("/filtering/set_url",
|
||||||
`{"url":"foo","data":{"id":0,"enabled":false,"url":"foo","name":"","rules_count":0,"whitelist":true},"whitelist":true}`,
|
`{"data":{"enabled":false,"name":"","url":"foo"},"url":"foo","whitelist":true}`,
|
||||||
`{"url":"bar","data":{"id":0,"enabled":false,"url":"bar","name":"","rules_count":0,"whitelist":true},"whitelist":true}`,
|
`{"data":{"enabled":false,"name":"","url":"bar"},"url":"bar","whitelist":true}`,
|
||||||
)
|
)
|
||||||
err := cl.UpdateFilters(true, types.Filter{URL: "foo"}, types.Filter{URL: "bar"})
|
err := cl.UpdateFilter(true, model.Filter{Url: "foo"})
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
err = cl.UpdateFilter(true, model.Filter{Url: "bar"})
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should delete Filters", func() {
|
It("should delete Filters", func() {
|
||||||
ts, cl = ClientPost("/filtering/remove_url",
|
ts, cl = ClientPost("/filtering/remove_url",
|
||||||
`{"id":0,"enabled":false,"url":"foo","name":"","rules_count":0,"whitelist":true}`,
|
`{"url":"foo","whitelist":true}`,
|
||||||
`{"id":0,"enabled":false,"url":"bar","name":"","rules_count":0,"whitelist":true}`,
|
`{"url":"bar","whitelist":true}`,
|
||||||
)
|
)
|
||||||
err := cl.DeleteFilters(true, types.Filter{URL: "foo"}, types.Filter{URL: "bar"})
|
err := cl.DeleteFilter(true, model.Filter{Url: "foo"})
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
err = cl.DeleteFilter(true, model.Filter{Url: "bar"})
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
})
|
It("should set empty filter rules", func() {
|
||||||
|
ts, cl = ClientPost("/filtering/set_rules",
|
||||||
Context("CustomRules", func() {
|
`{"rules":[]}`,
|
||||||
It("should set SetCustomRules", func() {
|
)
|
||||||
ts, cl = ClientPost("/filtering/set_rules", `foo
|
err := cl.SetCustomRules(utils.Ptr([]string{}))
|
||||||
bar`)
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
err := cl.SetCustomRules([]string{"foo", "bar"})
|
})
|
||||||
|
It("should set nil filter rules", func() {
|
||||||
|
ts, cl = ClientPost("/filtering/set_rules",
|
||||||
|
`{}`,
|
||||||
|
)
|
||||||
|
err := cl.SetCustomRules(nil)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -103,11 +119,11 @@ bar`)
|
|||||||
ts, cl = ClientGet("status.json", "/status")
|
ts, cl = ClientGet("status.json", "/status")
|
||||||
fs, err := cl.Status()
|
fs, err := cl.Status()
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(fs.DNSAddresses).Should(HaveLen(1))
|
Ω(fs.DnsAddresses).Should(HaveLen(1))
|
||||||
Ω(fs.DNSAddresses[0]).Should(Equal("192.168.1.2"))
|
Ω(fs.DnsAddresses[0]).Should(Equal("192.168.1.2"))
|
||||||
Ω(fs.Version).Should(Equal("v0.105.2"))
|
Ω(fs.Version).Should(Equal("v0.105.2"))
|
||||||
})
|
})
|
||||||
It("should return SetupNeededError", func() {
|
It("should return ErrSetupNeeded", func() {
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Location", "/install.html")
|
w.Header().Set("Location", "/install.html")
|
||||||
w.WriteHeader(http.StatusFound)
|
w.WriteHeader(http.StatusFound)
|
||||||
@@ -116,13 +132,20 @@ bar`)
|
|||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
_, err = cl.Status()
|
_, err = cl.Status()
|
||||||
Ω(err).Should(HaveOccurred())
|
Ω(err).Should(HaveOccurred())
|
||||||
Ω(err).Should(Equal(client.SetupNeededError))
|
Ω(err).Should(Equal(client.ErrSetupNeeded))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Context("Setup", func() {
|
Context("Setup", func() {
|
||||||
It("should add setup the instance", func() {
|
It("should add setup the instance", func() {
|
||||||
ts, cl = ClientPost("/install/configure", fmt.Sprintf(`{"web":{"ip":"0.0.0.0","port":3000,"status":"","can_autofix":false},"dns":{"ip":"0.0.0.0","port":53,"status":"","can_autofix":false},"username":"%s","password":"%s"}`, username, password))
|
ts, cl = ClientPost(
|
||||||
|
"/install/configure",
|
||||||
|
fmt.Sprintf(
|
||||||
|
`{"web":{"ip":"0.0.0.0","port":3000,"status":"","can_autofix":false},"dns":{"ip":"0.0.0.0","port":53,"status":"","can_autofix":false},"username":%q,"password":%q}`,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
),
|
||||||
|
)
|
||||||
err := cl.Setup()
|
err := cl.Setup()
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
@@ -136,13 +159,19 @@ bar`)
|
|||||||
Ω(*rwl).Should(HaveLen(2))
|
Ω(*rwl).Should(HaveLen(2))
|
||||||
})
|
})
|
||||||
It("should add RewriteList", func() {
|
It("should add RewriteList", func() {
|
||||||
ts, cl = ClientPost("/rewrite/add", `{"domain":"foo","answer":"foo"}`, `{"domain":"bar","answer":"bar"}`)
|
ts, cl = ClientPost("/rewrite/add", `{"answer":"foo","domain":"foo"}`, `{"answer":"bar","domain":"bar"}`)
|
||||||
err := cl.AddRewriteEntries(types.RewriteEntry{Answer: "foo", Domain: "foo"}, types.RewriteEntry{Answer: "bar", Domain: "bar"})
|
err := cl.AddRewriteEntries(
|
||||||
|
model.RewriteEntry{Answer: utils.Ptr("foo"), Domain: utils.Ptr("foo")},
|
||||||
|
model.RewriteEntry{Answer: utils.Ptr("bar"), Domain: utils.Ptr("bar")},
|
||||||
|
)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should delete RewriteList", func() {
|
It("should delete RewriteList", func() {
|
||||||
ts, cl = ClientPost("/rewrite/delete", `{"domain":"foo","answer":"foo"}`, `{"domain":"bar","answer":"bar"}`)
|
ts, cl = ClientPost("/rewrite/delete", `{"answer":"foo","domain":"foo"}`, `{"answer":"bar","domain":"bar"}`)
|
||||||
err := cl.DeleteRewriteEntries(types.RewriteEntry{Answer: "foo", Domain: "foo"}, types.RewriteEntry{Answer: "bar", Domain: "bar"})
|
err := cl.DeleteRewriteEntries(
|
||||||
|
model.RewriteEntry{Answer: utils.Ptr("foo"), Domain: utils.Ptr("foo")},
|
||||||
|
model.RewriteEntry{Answer: utils.Ptr("bar"), Domain: utils.Ptr("bar")},
|
||||||
|
)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -166,21 +195,22 @@ bar`)
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Context("SafeSearch", func() {
|
Context("SafeSearchConfig", func() {
|
||||||
It("should read safesearch status", func() {
|
It("should read safesearch status", func() {
|
||||||
ts, cl = ClientGet("safesearch-status.json", "/safesearch/status")
|
ts, cl = ClientGet("safesearch-status.json", "/safesearch/status")
|
||||||
ss, err := cl.SafeSearch()
|
ss, err := cl.SafeSearchConfig()
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(ss).Should(BeTrue())
|
Ω(ss.Enabled).ShouldNot(BeNil())
|
||||||
|
Ω(*ss.Enabled).Should(BeTrue())
|
||||||
})
|
})
|
||||||
It("should enable safesearch", func() {
|
It("should enable safesearch", func() {
|
||||||
ts, cl = ClientPost("/safesearch/enable", "")
|
ts, cl = ClientPut("/safesearch/settings", `{"enabled":true}`)
|
||||||
err := cl.ToggleSafeSearch(true)
|
err := cl.SetSafeSearchConfig(&model.SafeSearchConfig{Enabled: utils.Ptr(true)})
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should disable safesearch", func() {
|
It("should disable safesearch", func() {
|
||||||
ts, cl = ClientPost("/safesearch/disable", "")
|
ts, cl = ClientPut("/safesearch/settings", `{"enabled":false}`)
|
||||||
err := cl.ToggleSafeSearch(false)
|
err := cl.SetSafeSearchConfig(&model.SafeSearchConfig{Enabled: utils.Ptr(false)})
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -217,16 +247,25 @@ bar`)
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Context("Services", func() {
|
Context("BlockedServicesSchedule", func() {
|
||||||
It("should read Services", func() {
|
It("should read BlockedServicesSchedule", func() {
|
||||||
ts, cl = ClientGet("blockedservices-list.json", "/blocked_services/list")
|
ts, cl = ClientGet("blockedservicesschedule-get.json", "/blocked_services/get")
|
||||||
s, err := cl.Services()
|
s, err := cl.BlockedServicesSchedule()
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(s).Should(HaveLen(2))
|
Ω(*s.Ids).Should(HaveLen(3))
|
||||||
})
|
})
|
||||||
It("should set Services", func() {
|
It("should set BlockedServicesSchedule", func() {
|
||||||
ts, cl = ClientPost("/blocked_services/set", `["foo","bar"]`)
|
ts, cl = ClientPost("/blocked_services/update",
|
||||||
err := cl.SetServices([]string{"foo", "bar"})
|
`{"ids":["bar","foo"],"schedule":{"mon":{"end":99,"start":1}}}`)
|
||||||
|
err := cl.SetBlockedServicesSchedule(&model.BlockedServicesSchedule{
|
||||||
|
Ids: utils.Ptr([]string{"foo", "bar"}),
|
||||||
|
Schedule: &model.Schedule{
|
||||||
|
Mon: &model.DayRange{
|
||||||
|
Start: utils.Ptr(float32(1.0)),
|
||||||
|
End: utils.Ptr(float32(99.0)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -236,63 +275,78 @@ bar`)
|
|||||||
ts, cl = ClientGet("clients.json", "/clients")
|
ts, cl = ClientGet("clients.json", "/clients")
|
||||||
c, err := cl.Clients()
|
c, err := cl.Clients()
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(c.Clients).Should(HaveLen(2))
|
Ω(*c.Clients).Should(HaveLen(2))
|
||||||
})
|
})
|
||||||
It("should add Clients", func() {
|
It("should add Clients", func() {
|
||||||
ts, cl = ClientPost("/clients/add",
|
ts, cl = ClientPost("/clients/add",
|
||||||
`{"ids":["id"],"use_global_settings":false,"use_global_blocked_services":false,"name":"foo","filtering_enabled":false,"parental_enabled":false,"safesearch_enabled":false,"safebrowsing_enabled":false,"disallowed":false,"disallowed_rule":""}`,
|
`{"ids":["id"],"name":"foo"}`,
|
||||||
)
|
)
|
||||||
err := cl.AddClients(types.Client{Name: "foo", Ids: []string{"id"}})
|
err := cl.AddClient(&model.Client{Name: utils.Ptr("foo"), Ids: utils.Ptr([]string{"id"})})
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should update Clients", func() {
|
It("should update Clients", func() {
|
||||||
ts, cl = ClientPost("/clients/update",
|
ts, cl = ClientPost("/clients/update",
|
||||||
`{"name":"foo","data":{"ids":["id"],"use_global_settings":false,"use_global_blocked_services":false,"name":"foo","filtering_enabled":false,"parental_enabled":false,"safesearch_enabled":false,"safebrowsing_enabled":false,"disallowed":false,"disallowed_rule":""}}`,
|
`{"data":{"ids":["id"],"name":"foo"},"name":"foo"}`,
|
||||||
)
|
)
|
||||||
err := cl.UpdateClients(types.Client{Name: "foo", Ids: []string{"id"}})
|
err := cl.UpdateClient(&model.Client{Name: utils.Ptr("foo"), Ids: utils.Ptr([]string{"id"})})
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should delete Clients", func() {
|
It("should delete Clients", func() {
|
||||||
ts, cl = ClientPost("/clients/delete",
|
ts, cl = ClientPost("/clients/delete",
|
||||||
`{"ids":["id"],"use_global_settings":false,"use_global_blocked_services":false,"name":"foo","filtering_enabled":false,"parental_enabled":false,"safesearch_enabled":false,"safebrowsing_enabled":false,"disallowed":false,"disallowed_rule":""}`,
|
`{"ids":["id"],"name":"foo"}`,
|
||||||
)
|
)
|
||||||
err := cl.DeleteClients(types.Client{Name: "foo", Ids: []string{"id"}})
|
err := cl.DeleteClient(&model.Client{Name: utils.Ptr("foo"), Ids: utils.Ptr([]string{"id"})})
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Context("QueryLogConfig", func() {
|
Context("QueryLogConfig", func() {
|
||||||
It("should read QueryLogConfig", func() {
|
It("should read QueryLogConfig", func() {
|
||||||
ts, cl = ClientGet("querylog_info.json", "/querylog_info")
|
ts, cl = ClientGet("querylog_config.json", "/querylog/config")
|
||||||
qlc, err := cl.QueryLogConfig()
|
qlc, err := cl.QueryLogConfig()
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(qlc.Enabled).Should(BeTrue())
|
Ω(qlc.Enabled).ShouldNot(BeNil())
|
||||||
Ω(qlc.Interval).Should(Equal(90))
|
Ω(*qlc.Enabled).Should(BeTrue())
|
||||||
|
Ω(qlc.Interval).ShouldNot(BeNil())
|
||||||
|
Ω(*qlc.Interval).Should(Equal(model.QueryLogConfigInterval(90)))
|
||||||
})
|
})
|
||||||
It("should set QueryLogConfig", func() {
|
It("should set QueryLogConfig", func() {
|
||||||
ts, cl = ClientPost("/querylog_config", `{"enabled":true,"interval":123,"anonymize_client_ip":true}`)
|
ts, cl = ClientPut(
|
||||||
err := cl.SetQueryLogConfig(true, 123, true)
|
"/querylog/config/update",
|
||||||
|
`{"anonymize_client_ip":true,"enabled":true,"interval":123,"ignored":["foo.bar"]}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
var interval model.QueryLogConfigInterval = 123
|
||||||
|
err := cl.SetQueryLogConfig(&model.QueryLogConfigWithIgnored{
|
||||||
|
QueryLogConfig: model.QueryLogConfig{
|
||||||
|
AnonymizeClientIp: utils.Ptr(true),
|
||||||
|
Interval: &interval,
|
||||||
|
Enabled: utils.Ptr(true),
|
||||||
|
},
|
||||||
|
Ignored: []string{"foo.bar"},
|
||||||
|
})
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
Context("StatsConfig", func() {
|
Context("StatsConfig", func() {
|
||||||
It("should read StatsConfig", func() {
|
It("should read StatsConfig", func() {
|
||||||
ts, cl = ClientGet("stats_info.json", "/stats_info")
|
ts, cl = ClientGet("stats_info.json", "/stats/config")
|
||||||
sc, err := cl.StatsConfig()
|
sc, err := cl.StatsConfig()
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(sc.Interval).Should(Equal(1))
|
Ω(sc.Interval).ShouldNot(BeNil())
|
||||||
|
Ω(sc.Interval).Should(Equal(float32(1)))
|
||||||
})
|
})
|
||||||
It("should set StatsConfig", func() {
|
It("should set StatsConfig", func() {
|
||||||
ts, cl = ClientPost("/stats_config", `{"interval":123}`)
|
ts, cl = ClientPost("/stats/config/update", `{"enabled":false,"ignored":null,"interval":123}`)
|
||||||
err := cl.SetStatsConfig(123)
|
|
||||||
|
var interval float32 = 123
|
||||||
|
err := cl.SetStatsConfig(&model.PutStatsConfigUpdateRequest{Interval: interval})
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Context("helper functions", func() {
|
Context("helper functions", func() {
|
||||||
var (
|
var cl client.Client
|
||||||
cl client.Client
|
|
||||||
)
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
@@ -311,7 +365,8 @@ bar`)
|
|||||||
|
|
||||||
Context("doPost", func() {
|
Context("doPost", func() {
|
||||||
It("should return an error on status code != 200", func() {
|
It("should return an error on status code != 200", func() {
|
||||||
err := cl.SetStatsConfig(123)
|
var interval float32 = 123
|
||||||
|
err := cl.SetStatsConfig(&model.PutStatsConfigUpdateRequest{Interval: interval})
|
||||||
Ω(err).Should(HaveOccurred())
|
Ω(err).Should(HaveOccurred())
|
||||||
Ω(err.Error()).Should(Equal("401 Unauthorized"))
|
Ω(err.Error()).Should(Equal("401 Unauthorized"))
|
||||||
})
|
})
|
||||||
@@ -319,10 +374,10 @@ bar`)
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
func ClientGet(file string, path string) (*httptest.Server, client.Client) {
|
func ClientGet(file, path string) (*httptest.Server, client.Client) {
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
Ω(r.URL.Path).Should(Equal(types.DefaultAPIPath + path))
|
Ω(r.URL.Path).Should(Equal(types.DefaultAPIPath + path))
|
||||||
b, err := ioutil.ReadFile(filepath.Join("../../testdata", file))
|
b, err := os.ReadFile(filepath.Join("..", "..", "testdata", file))
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
_, err = w.Write(b)
|
_, err = w.Write(b)
|
||||||
@@ -337,7 +392,22 @@ func ClientPost(path string, content ...string) (*httptest.Server, client.Client
|
|||||||
index := 0
|
index := 0
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
Ω(r.URL.Path).Should(Equal(types.DefaultAPIPath + path))
|
Ω(r.URL.Path).Should(Equal(types.DefaultAPIPath + path))
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := io.ReadAll(r.Body)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(body).Should(Equal([]byte(content[index])))
|
||||||
|
index++
|
||||||
|
}))
|
||||||
|
|
||||||
|
cl, err := client.New(types.AdGuardInstance{URL: ts.URL, Username: username, Password: password})
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
return ts, cl
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClientPut(path string, content ...string) (*httptest.Server, client.Client) {
|
||||||
|
index := 0
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
Ω(r.URL.Path).Should(Equal(types.DefaultAPIPath + path))
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(body).Should(Equal([]byte(content[index])))
|
Ω(body).Should(Equal([]byte(content[index])))
|
||||||
index++
|
index++
|
||||||
144
internal/client/model/client/client.go
Normal file
144
internal/client/model/client/client.go
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/log"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var l = log.GetLogger("client")
|
||||||
|
|
||||||
|
// New create a new api client.
|
||||||
|
func New(config types.AdGuardInstance) (Client, error) {
|
||||||
|
var apiURL string
|
||||||
|
if config.APIPath == "" {
|
||||||
|
apiURL = config.URL + "/control"
|
||||||
|
} else {
|
||||||
|
apiURL = fmt.Sprintf("%s/%s", config.URL, config.APIPath)
|
||||||
|
}
|
||||||
|
u, err := url.Parse(apiURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
u.Path = path.Clean(u.Path)
|
||||||
|
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
// #nosec G402 has to be explicitly enabled
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
aghClient, err := model.NewClient(u.String(), func(client *model.AdguardHomeClient) error {
|
||||||
|
client.Client = httpClient
|
||||||
|
client.RequestEditors = append(client.RequestEditors, func(ctx context.Context, req *http.Request) error {
|
||||||
|
if config.Username != "" && config.Password != "" {
|
||||||
|
req.Header.Add("Authorization", "Basic "+basicAuth(config.Username, config.Password))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &apiClient{
|
||||||
|
host: u.Host,
|
||||||
|
client: aghClient,
|
||||||
|
log: l.With("host", u.Host),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func basicAuth(username, password string) string {
|
||||||
|
auth := username + ":" + password
|
||||||
|
return base64.StdEncoding.EncodeToString([]byte(auth))
|
||||||
|
}
|
||||||
|
|
||||||
|
type apiClient struct {
|
||||||
|
host string
|
||||||
|
client *model.AdguardHomeClient
|
||||||
|
log *zap.SugaredLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a apiClient) Host(context.Context) string {
|
||||||
|
return a.host
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a apiClient) GetServerStatus(ctx context.Context) (*model.ServerStatus, error) {
|
||||||
|
sr, err := read(ctx, a.client.Status, model.ParseStatusResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sr.JSON200, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a apiClient) GetFilteringStatus(ctx context.Context) (*model.FilterStatus, error) {
|
||||||
|
sr, err := read(ctx, a.client.FilteringStatus, model.ParseFilteringStatusResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sr.JSON200, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a apiClient) SetFilteringConfig(ctx context.Context, config model.FilterConfig) error {
|
||||||
|
return write(ctx, config, a.client.FilteringConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func write[B any](
|
||||||
|
ctx context.Context,
|
||||||
|
body B,
|
||||||
|
req func(ctx context.Context, body B, reqEditors ...model.RequestEditorFn) (*http.Response, error),
|
||||||
|
) error {
|
||||||
|
resp, err := req(ctx, body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return detailedError(resp)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func read[I any](
|
||||||
|
ctx context.Context,
|
||||||
|
req func(ctx context.Context, reqEditors ...model.RequestEditorFn) (*http.Response, error),
|
||||||
|
parse func(rsp *http.Response) (*I, error),
|
||||||
|
) (*I, error) {
|
||||||
|
resp, err := req(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, detailedError(resp)
|
||||||
|
}
|
||||||
|
return parse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func detailedError(resp *http.Response) error {
|
||||||
|
e := resp.Status
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(body) > 0 {
|
||||||
|
e += fmt.Sprintf("(%s)", string(body))
|
||||||
|
}
|
||||||
|
return errors.New(e)
|
||||||
|
}
|
||||||
15
internal/client/model/client/interface.go
Normal file
15
internal/client/model/client/interface.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client interface {
|
||||||
|
Host(ctx context.Context) string
|
||||||
|
GetServerStatus(ctx context.Context) (*model.ServerStatus, error)
|
||||||
|
|
||||||
|
GetFilteringStatus(ctx context.Context) (*model.FilterStatus, error)
|
||||||
|
SetFilteringConfig(ctx context.Context, config model.FilterConfig) error
|
||||||
|
}
|
||||||
28
internal/client/model/client/resty.go
Normal file
28
internal/client/model/client/resty.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ model.HttpRequestDoer = &adapter{}
|
||||||
|
|
||||||
|
func RestyAdapter(r *resty.Client) model.HttpRequestDoer {
|
||||||
|
return &adapter{
|
||||||
|
client: r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type adapter struct {
|
||||||
|
client *resty.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a adapter) Do(req *http.Request) (*http.Response, error) {
|
||||||
|
r, err := a.client.R().
|
||||||
|
SetHeaderMultiValues(req.Header).
|
||||||
|
Execute(req.Method, req.URL.String())
|
||||||
|
return r.RawResponse, err
|
||||||
|
}
|
||||||
496
internal/client/model/model-functions.go
Normal file
496
internal/client/model/model-functions.go
Normal file
@@ -0,0 +1,496 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jinzhu/copier"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"k8s.io/utils/ptr"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Clone the config.
|
||||||
|
func (c *DhcpStatus) Clone() *DhcpStatus {
|
||||||
|
clone := &DhcpStatus{}
|
||||||
|
_ = copier.Copy(clone, c)
|
||||||
|
return clone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DhcpStatus) cleanV4V6() {
|
||||||
|
if c.V4 != nil && !c.V4.isValid() {
|
||||||
|
c.V4 = nil
|
||||||
|
}
|
||||||
|
if c.V6 != nil && !c.V6.isValid() {
|
||||||
|
c.V6 = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanAndEquals dhcp server config equal check where V4 and V6 are cleaned in advance.
|
||||||
|
func (c *DhcpStatus) CleanAndEquals(o *DhcpStatus) bool {
|
||||||
|
c.cleanV4V6()
|
||||||
|
o.cleanV4V6()
|
||||||
|
return c.Equals(o)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals dhcp server config equal check.
|
||||||
|
func (c *DhcpStatus) Equals(o *DhcpStatus) bool {
|
||||||
|
return utils.JSONEquals(c, o)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DhcpStatus) HasConfig() bool {
|
||||||
|
return (c.V4 != nil && c.V4.isValid()) || (c.V6 != nil && c.V6.isValid())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j DhcpConfigV4) isValid() bool {
|
||||||
|
return j.GatewayIp != nil && *j.GatewayIp != "" &&
|
||||||
|
j.SubnetMask != nil && *j.SubnetMask != "" &&
|
||||||
|
j.RangeStart != nil && *j.RangeStart != "" &&
|
||||||
|
j.RangeEnd != nil && *j.RangeEnd != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j DhcpConfigV6) isValid() bool {
|
||||||
|
return j.RangeStart != nil && *j.RangeStart != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type DhcpStaticLeases []DhcpStaticLease
|
||||||
|
|
||||||
|
// MergeDhcpStaticLeases the leases.
|
||||||
|
func MergeDhcpStaticLeases(l, other *[]DhcpStaticLease) (adds, removes DhcpStaticLeases) {
|
||||||
|
var thisLeases []DhcpStaticLease
|
||||||
|
var otherLeases []DhcpStaticLease
|
||||||
|
|
||||||
|
if l != nil {
|
||||||
|
thisLeases = *l
|
||||||
|
}
|
||||||
|
if other != nil {
|
||||||
|
otherLeases = *other
|
||||||
|
}
|
||||||
|
current := make(map[string]DhcpStaticLease)
|
||||||
|
|
||||||
|
for _, le := range thisLeases {
|
||||||
|
current[le.Mac] = le
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, le := range otherLeases {
|
||||||
|
if _, ok := current[le.Mac]; ok {
|
||||||
|
delete(current, le.Mac)
|
||||||
|
} else {
|
||||||
|
adds = append(adds, le)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rr := range current {
|
||||||
|
removes = append(removes, rr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return adds, removes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals dns config equal check.
|
||||||
|
func (c *DNSConfig) Equals(o *DNSConfig) bool {
|
||||||
|
cc := c.Clone()
|
||||||
|
oo := o.Clone()
|
||||||
|
cc.Sort()
|
||||||
|
oo.Sort()
|
||||||
|
|
||||||
|
return utils.JSONEquals(cc, oo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DNSConfig) Clone() *DNSConfig {
|
||||||
|
return utils.Clone(c, &DNSConfig{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort dns config.
|
||||||
|
func (c *DNSConfig) Sort() {
|
||||||
|
if c.UpstreamDns != nil {
|
||||||
|
sort.Strings(*c.UpstreamDns)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.UpstreamDns != nil {
|
||||||
|
sort.Strings(*c.BootstrapDns)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.UpstreamDns != nil {
|
||||||
|
sort.Strings(*c.LocalPtrUpstreams)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals access list equal check.
|
||||||
|
func (al *AccessList) Equals(o *AccessList) bool {
|
||||||
|
return EqualsStringSlice(al.AllowedClients, o.AllowedClients, true) &&
|
||||||
|
EqualsStringSlice(al.DisallowedClients, o.DisallowedClients, true) &&
|
||||||
|
EqualsStringSlice(al.BlockedHosts, o.BlockedHosts, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func EqualsStringSlice(a, b *[]string, sortIt bool) bool {
|
||||||
|
if a == nil && b == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if a == nil || b == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
aa := *a
|
||||||
|
bb := *b
|
||||||
|
if sortIt {
|
||||||
|
sort.Strings(aa)
|
||||||
|
sort.Strings(bb)
|
||||||
|
}
|
||||||
|
if len(aa) != len(bb) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, v := range aa {
|
||||||
|
if v != bb[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort clients.
|
||||||
|
func (cl *Client) Sort() {
|
||||||
|
if cl.Ids != nil {
|
||||||
|
sort.Strings(*cl.Ids)
|
||||||
|
}
|
||||||
|
if cl.Tags != nil {
|
||||||
|
sort.Strings(*cl.Tags)
|
||||||
|
}
|
||||||
|
if cl.BlockedServices != nil {
|
||||||
|
sort.Strings(*cl.BlockedServices)
|
||||||
|
}
|
||||||
|
if cl.Upstreams != nil {
|
||||||
|
sort.Strings(*cl.Upstreams)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrepareDiff so we skip it in diff.
|
||||||
|
func (cl *Client) PrepareDiff() *string {
|
||||||
|
var tz *string
|
||||||
|
bss := cl.BlockedServicesSchedule
|
||||||
|
if bss != nil && bss.Mon == nil && bss.Tue == nil && bss.Wed == nil &&
|
||||||
|
bss.Thu == nil && bss.Fri == nil && bss.Sat == nil && bss.Sun == nil {
|
||||||
|
tz = cl.BlockedServicesSchedule.TimeZone
|
||||||
|
cl.BlockedServicesSchedule.TimeZone = nil
|
||||||
|
}
|
||||||
|
return tz
|
||||||
|
}
|
||||||
|
|
||||||
|
// AfterDiff reset after diff.
|
||||||
|
func (cl *Client) AfterDiff(tz *string) {
|
||||||
|
if cl.BlockedServicesSchedule != nil {
|
||||||
|
cl.BlockedServicesSchedule.TimeZone = tz
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals Clients equal check.
|
||||||
|
func (cl *Client) Equals(o *Client) bool {
|
||||||
|
cl.Sort()
|
||||||
|
o.Sort()
|
||||||
|
|
||||||
|
bssCl := cl.PrepareDiff()
|
||||||
|
bssO := o.PrepareDiff()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
cl.AfterDiff(bssCl)
|
||||||
|
o.AfterDiff(bssO)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return utils.JSONEquals(cl, o)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add ac client.
|
||||||
|
func (clients *Clients) Add(cl Client) {
|
||||||
|
if clients.Clients == nil {
|
||||||
|
clients.Clients = &ClientsArray{cl}
|
||||||
|
} else {
|
||||||
|
a := append(*clients.Clients, cl)
|
||||||
|
clients.Clients = &a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge merge Clients.
|
||||||
|
func (clients *Clients) Merge(other *Clients) (adds, removes, updates []*Client) {
|
||||||
|
current := make(map[string]*Client)
|
||||||
|
if clients.Clients != nil {
|
||||||
|
cc := *clients.Clients
|
||||||
|
for _, client := range cc {
|
||||||
|
current[*client.Name] = &client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := make(map[string]*Client)
|
||||||
|
if other.Clients != nil {
|
||||||
|
oc := *other.Clients
|
||||||
|
for _, client := range oc {
|
||||||
|
expected[*client.Name] = &client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cl := range expected {
|
||||||
|
if oc, ok := current[*cl.Name]; ok {
|
||||||
|
if !cl.Equals(oc) {
|
||||||
|
updates = append(updates, cl)
|
||||||
|
}
|
||||||
|
delete(current, *cl.Name)
|
||||||
|
} else {
|
||||||
|
adds = append(adds, cl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rr := range current {
|
||||||
|
removes = append(removes, rr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return adds, updates, removes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key RewriteEntry key.
|
||||||
|
func (re *RewriteEntry) Key() string {
|
||||||
|
var d string
|
||||||
|
var a string
|
||||||
|
if re.Domain != nil {
|
||||||
|
d = *re.Domain
|
||||||
|
}
|
||||||
|
if re.Answer != nil {
|
||||||
|
a = *re.Answer
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s#%s", d, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RewriteEntries list of RewriteEntry.
|
||||||
|
type RewriteEntries []RewriteEntry
|
||||||
|
|
||||||
|
// Merge RewriteEntries.
|
||||||
|
func (rwe *RewriteEntries) Merge(other *RewriteEntries) (adds, removes, duplicates RewriteEntries) {
|
||||||
|
current := make(map[string]RewriteEntry)
|
||||||
|
|
||||||
|
processed := make(map[string]bool)
|
||||||
|
for _, rr := range *rwe {
|
||||||
|
if _, ok := processed[rr.Key()]; !ok {
|
||||||
|
current[rr.Key()] = rr
|
||||||
|
processed[rr.Key()] = true
|
||||||
|
} else {
|
||||||
|
// remove duplicate
|
||||||
|
removes = append(removes, rr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rr := range *other {
|
||||||
|
if _, ok := current[rr.Key()]; ok {
|
||||||
|
delete(current, rr.Key())
|
||||||
|
} else {
|
||||||
|
if _, ok := processed[rr.Key()]; !ok {
|
||||||
|
adds = append(adds, rr)
|
||||||
|
processed[rr.Key()] = true
|
||||||
|
} else {
|
||||||
|
// skip duplicate
|
||||||
|
duplicates = append(duplicates, rr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rr := range current {
|
||||||
|
removes = append(removes, rr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return adds, removes, duplicates
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeFilters(this, other *[]Filter) (adds, updates, removes []Filter) {
|
||||||
|
if this == nil && other == nil {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
current := make(map[string]*Filter)
|
||||||
|
|
||||||
|
if this != nil {
|
||||||
|
for _, fi := range *this {
|
||||||
|
current[fi.Url] = &fi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if other != nil {
|
||||||
|
for _, rr := range *other {
|
||||||
|
if c, ok := current[rr.Url]; ok {
|
||||||
|
if !c.Equals(&rr) {
|
||||||
|
updates = append(updates, rr)
|
||||||
|
}
|
||||||
|
delete(current, rr.Url)
|
||||||
|
} else {
|
||||||
|
adds = append(adds, rr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rr := range current {
|
||||||
|
removes = append(removes, *rr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return adds, updates, removes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals Filter equal check.
|
||||||
|
func (f *Filter) Equals(o *Filter) bool {
|
||||||
|
return f.Enabled == o.Enabled && f.Url == o.Url && f.Name == o.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryLogConfigWithIgnored struct {
|
||||||
|
QueryLogConfig
|
||||||
|
|
||||||
|
// Ignored List of host names, which should not be written to log
|
||||||
|
Ignored []string `json:"ignored,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals QueryLogConfig equal check.
|
||||||
|
func (qlc *QueryLogConfigWithIgnored) Equals(o *QueryLogConfigWithIgnored) bool {
|
||||||
|
return utils.JSONEquals(qlc, o)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals QueryLogConfigInterval equal check.
|
||||||
|
func (qlc *QueryLogConfigInterval) Equals(o *QueryLogConfigInterval) bool {
|
||||||
|
return ptrEquals(qlc, o)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ptrEquals[T comparable](a, b *T) bool {
|
||||||
|
if a == nil && b == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
var aa T
|
||||||
|
if a != nil {
|
||||||
|
aa = *a
|
||||||
|
}
|
||||||
|
var bb T
|
||||||
|
if b != nil {
|
||||||
|
bb = *b
|
||||||
|
}
|
||||||
|
|
||||||
|
return aa == bb
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableConfig API struct.
|
||||||
|
type EnableConfig struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ssc *SafeSearchConfig) Equals(o *SafeSearchConfig) bool {
|
||||||
|
return ptrEquals(ssc.Enabled, o.Enabled) &&
|
||||||
|
ptrEquals(ssc.Bing, o.Bing) &&
|
||||||
|
ptrEquals(ssc.Duckduckgo, o.Duckduckgo) &&
|
||||||
|
ptrEquals(ssc.Google, o.Google) &&
|
||||||
|
ptrEquals(ssc.Pixabay, o.Pixabay) &&
|
||||||
|
ptrEquals(ssc.Yandex, o.Yandex) &&
|
||||||
|
ptrEquals(ssc.Youtube, o.Youtube)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pi *ProfileInfo) Equals(o *ProfileInfo, withTheme bool) bool {
|
||||||
|
return pi.Language == o.Language && (!withTheme || pi.Theme == o.Theme)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pi *ProfileInfo) ShouldSyncFor(o *ProfileInfo, withTheme bool) *ProfileInfo {
|
||||||
|
if pi.Equals(o, withTheme) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
merged := &ProfileInfo{Name: pi.Name, Language: pi.Language, Theme: pi.Theme}
|
||||||
|
if o.Language != "" {
|
||||||
|
merged.Language = o.Language
|
||||||
|
}
|
||||||
|
if withTheme && o.Theme != "" {
|
||||||
|
merged.Theme = o.Theme
|
||||||
|
}
|
||||||
|
if merged.Name == "" || merged.Language == "" || merged.Equals(pi, false) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return merged
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bss *BlockedServicesSchedule) Equals(o *BlockedServicesSchedule) bool {
|
||||||
|
return utils.JSONEquals(bss, o)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bss *BlockedServicesSchedule) ServicesString() string {
|
||||||
|
return ArrayString(bss.Ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ArrayString(a *[]string) string {
|
||||||
|
if a == nil {
|
||||||
|
return "[]"
|
||||||
|
}
|
||||||
|
sorted := *a
|
||||||
|
sort.Strings(sorted)
|
||||||
|
return fmt.Sprintf("[%s]", strings.Join(sorted, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DNSConfig) Sanitize(l *zap.SugaredLogger) {
|
||||||
|
// disable UsePrivatePtrResolvers if not configured
|
||||||
|
// https://github.com/AdguardTeam/AdGuardHome/issues/6820
|
||||||
|
if c.UsePrivatePtrResolvers != nil && *c.UsePrivatePtrResolvers &&
|
||||||
|
(c.LocalPtrUpstreams == nil || len(*c.LocalPtrUpstreams) == 0) {
|
||||||
|
l.Warn(
|
||||||
|
"disabling replica 'Use private reverse DNS resolvers' as no 'Private reverse DNS servers' are configured on origin",
|
||||||
|
)
|
||||||
|
c.UsePrivatePtrResolvers = utils.Ptr(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals GetStatsConfigResponse equal check.
|
||||||
|
func (sc *GetStatsConfigResponse) Equals(o *GetStatsConfigResponse) bool {
|
||||||
|
return utils.JSONEquals(sc, o)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStats() *Stats {
|
||||||
|
return &Stats{
|
||||||
|
NumBlockedFiltering: ptr.To(0),
|
||||||
|
NumReplacedParental: ptr.To(0),
|
||||||
|
NumReplacedSafesearch: ptr.To(0),
|
||||||
|
NumReplacedSafebrowsing: ptr.To(0),
|
||||||
|
NumDnsQueries: ptr.To(0),
|
||||||
|
|
||||||
|
BlockedFiltering: ptr.To(make([]int, 24)),
|
||||||
|
DnsQueries: ptr.To(make([]int, 24)),
|
||||||
|
ReplacedParental: ptr.To(make([]int, 24)),
|
||||||
|
ReplacedSafebrowsing: ptr.To(make([]int, 24)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stats) Add(other *Stats) {
|
||||||
|
s.NumBlockedFiltering = addInt(s.NumBlockedFiltering, other.NumBlockedFiltering)
|
||||||
|
s.NumReplacedSafebrowsing = addInt(s.NumReplacedSafebrowsing, other.NumReplacedSafebrowsing)
|
||||||
|
s.NumDnsQueries = addInt(s.NumDnsQueries, other.NumDnsQueries)
|
||||||
|
s.NumReplacedSafesearch = addInt(s.NumReplacedSafesearch, other.NumReplacedSafesearch)
|
||||||
|
s.NumReplacedParental = addInt(s.NumReplacedParental, other.NumReplacedParental)
|
||||||
|
|
||||||
|
s.BlockedFiltering = sumUp(s.BlockedFiltering, other.BlockedFiltering)
|
||||||
|
s.DnsQueries = sumUp(s.DnsQueries, other.DnsQueries)
|
||||||
|
s.ReplacedParental = sumUp(s.ReplacedParental, other.ReplacedParental)
|
||||||
|
s.ReplacedSafebrowsing = sumUp(s.ReplacedSafebrowsing, other.ReplacedSafebrowsing)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addInt(t, add *int) *int {
|
||||||
|
if add != nil {
|
||||||
|
return ptr.To(*t + *add)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func sumUp(t, o *[]int) *[]int {
|
||||||
|
if o != nil {
|
||||||
|
tt := *t
|
||||||
|
oo := *o
|
||||||
|
var sum []int
|
||||||
|
for i := range tt {
|
||||||
|
if len(oo) >= i {
|
||||||
|
sum = append(sum, tt[i]+oo[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &sum
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TlsConfig) Equals(config *TlsConfig) bool {
|
||||||
|
return utils.JSONEquals(c, config)
|
||||||
|
}
|
||||||
10825
internal/client/model/model_generated.go
Normal file
10825
internal/client/model/model_generated.go
Normal file
File diff suppressed because it is too large
Load Diff
104
internal/client/model/model_private_test.go
Normal file
104
internal/client/model/model_private_test.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
"github.com/onsi/gomega"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/log"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Types", func() {
|
||||||
|
Context("DhcpConfigV4", func() {
|
||||||
|
DescribeTable("DhcpConfigV4 should not be valid",
|
||||||
|
func(v4 DhcpConfigV4) {
|
||||||
|
gomega.Ω(v4.isValid()).Should(gomega.BeFalse())
|
||||||
|
},
|
||||||
|
Entry(`When GatewayIp is nil`, DhcpConfigV4{
|
||||||
|
GatewayIp: nil,
|
||||||
|
SubnetMask: utils.Ptr("2.2.2.2"),
|
||||||
|
RangeStart: utils.Ptr("3.3.3.3"),
|
||||||
|
RangeEnd: utils.Ptr("4.4.4.4"),
|
||||||
|
}),
|
||||||
|
Entry(`When GatewayIp is ""`, DhcpConfigV4{
|
||||||
|
GatewayIp: utils.Ptr(""),
|
||||||
|
SubnetMask: utils.Ptr("2.2.2.2"),
|
||||||
|
RangeStart: utils.Ptr("3.3.3.3"),
|
||||||
|
RangeEnd: utils.Ptr("4.4.4.4"),
|
||||||
|
}),
|
||||||
|
Entry(`When SubnetMask is nil`, DhcpConfigV4{
|
||||||
|
GatewayIp: utils.Ptr("1.1.1.1"),
|
||||||
|
SubnetMask: nil,
|
||||||
|
RangeStart: utils.Ptr("3.3.3.3"),
|
||||||
|
RangeEnd: utils.Ptr("4.4.4.4"),
|
||||||
|
}),
|
||||||
|
Entry(`When SubnetMask is ""`, DhcpConfigV4{
|
||||||
|
GatewayIp: utils.Ptr("1.1.1.1"),
|
||||||
|
SubnetMask: utils.Ptr(""),
|
||||||
|
RangeStart: utils.Ptr("3.3.3.3"),
|
||||||
|
RangeEnd: utils.Ptr("4.4.4.4"),
|
||||||
|
}),
|
||||||
|
Entry(`When SubnetMask is nil`, DhcpConfigV4{
|
||||||
|
GatewayIp: utils.Ptr("1.1.1.1"),
|
||||||
|
SubnetMask: utils.Ptr("2.2.2.2"),
|
||||||
|
RangeStart: nil,
|
||||||
|
RangeEnd: utils.Ptr("4.4.4.4"),
|
||||||
|
}),
|
||||||
|
Entry(`When SubnetMask is ""`, DhcpConfigV4{
|
||||||
|
GatewayIp: utils.Ptr("1.1.1.1"),
|
||||||
|
SubnetMask: utils.Ptr("2.2.2.2"),
|
||||||
|
RangeStart: utils.Ptr(""),
|
||||||
|
RangeEnd: utils.Ptr("4.4.4.4"),
|
||||||
|
}),
|
||||||
|
Entry(`When RangeEnd is nil`, DhcpConfigV4{
|
||||||
|
GatewayIp: utils.Ptr("1.1.1.1"),
|
||||||
|
SubnetMask: utils.Ptr("2.2.2.2"),
|
||||||
|
RangeStart: utils.Ptr("3.3.3.3"),
|
||||||
|
RangeEnd: nil,
|
||||||
|
}),
|
||||||
|
Entry(`When RangeEnd is ""`, DhcpConfigV4{
|
||||||
|
GatewayIp: utils.Ptr("1.1.1.1"),
|
||||||
|
SubnetMask: utils.Ptr("2.2.2.2"),
|
||||||
|
RangeStart: utils.Ptr("3.3.3.3"),
|
||||||
|
RangeEnd: utils.Ptr(""),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
Context("DhcpConfigV6", func() {
|
||||||
|
DescribeTable("DhcpConfigV6 should not be valid",
|
||||||
|
func(v6 DhcpConfigV6) {
|
||||||
|
gomega.Ω(v6.isValid()).Should(gomega.BeFalse())
|
||||||
|
},
|
||||||
|
Entry(`When SubnetMask is nil`, DhcpConfigV6{RangeStart: nil}),
|
||||||
|
Entry(`When SubnetMask is ""`, DhcpConfigV6{RangeStart: utils.Ptr("")}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
Context("DNSConfig", func() {
|
||||||
|
var (
|
||||||
|
cfg *DNSConfig
|
||||||
|
l *zap.SugaredLogger
|
||||||
|
)
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
cfg = &DNSConfig{
|
||||||
|
UsePrivatePtrResolvers: utils.Ptr(true),
|
||||||
|
}
|
||||||
|
l = log.GetLogger("test")
|
||||||
|
})
|
||||||
|
Context("Sanitize", func() {
|
||||||
|
It("should disable UsePrivatePtrResolvers resolvers is nil ", func() {
|
||||||
|
cfg.LocalPtrUpstreams = nil
|
||||||
|
cfg.Sanitize(l)
|
||||||
|
gomega.Ω(cfg.UsePrivatePtrResolvers).ShouldNot(gomega.BeNil())
|
||||||
|
gomega.Ω(*cfg.UsePrivatePtrResolvers).Should(gomega.Equal(false))
|
||||||
|
})
|
||||||
|
It("should disable UsePrivatePtrResolvers resolvers is empty ", func() {
|
||||||
|
cfg.LocalPtrUpstreams = utils.Ptr([]string{})
|
||||||
|
cfg.Sanitize(l)
|
||||||
|
gomega.Ω(cfg.UsePrivatePtrResolvers).ShouldNot(gomega.BeNil())
|
||||||
|
gomega.Ω(*cfg.UsePrivatePtrResolvers).Should(gomega.Equal(false))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
13
internal/client/model/model_suite_test.go
Normal file
13
internal/client/model/model_suite_test.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package model_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTypes(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Model Suite")
|
||||||
|
}
|
||||||
470
internal/client/model/model_test.go
Normal file
470
internal/client/model/model_test.go
Normal file
@@ -0,0 +1,470 @@
|
|||||||
|
package model_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Types", func() {
|
||||||
|
var (
|
||||||
|
url string
|
||||||
|
apiPath string
|
||||||
|
)
|
||||||
|
BeforeEach(func() {
|
||||||
|
url = "https://" + uuid.NewString()
|
||||||
|
apiPath = "/" + uuid.NewString()
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("FilteringStatus", func() {
|
||||||
|
It("should correctly parse json", func() {
|
||||||
|
b, err := os.ReadFile("../../../testdata/filtering-status.json")
|
||||||
|
fs := &model.FilterStatus{}
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
err = json.Unmarshal(b, fs)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("Filters", func() {
|
||||||
|
Context("Merge", func() {
|
||||||
|
var (
|
||||||
|
originFilters []model.Filter
|
||||||
|
replicaFilters []model.Filter
|
||||||
|
)
|
||||||
|
BeforeEach(func() {
|
||||||
|
originFilters = []model.Filter{}
|
||||||
|
replicaFilters = []model.Filter{}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should add a missing filter", func() {
|
||||||
|
originFilters = append(originFilters, model.Filter{Url: url})
|
||||||
|
a, u, d := model.MergeFilters(&replicaFilters, &originFilters)
|
||||||
|
Ω(a).Should(HaveLen(1))
|
||||||
|
Ω(u).Should(BeEmpty())
|
||||||
|
Ω(d).Should(BeEmpty())
|
||||||
|
|
||||||
|
Ω(a[0].Url).Should(Equal(url))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should remove additional filter", func() {
|
||||||
|
replicaFilters = append(replicaFilters, model.Filter{Url: url})
|
||||||
|
a, u, d := model.MergeFilters(&replicaFilters, &originFilters)
|
||||||
|
Ω(a).Should(BeEmpty())
|
||||||
|
Ω(u).Should(BeEmpty())
|
||||||
|
Ω(d).Should(HaveLen(1))
|
||||||
|
|
||||||
|
Ω(d[0].Url).Should(Equal(url))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should update existing filter when enabled differs", func() {
|
||||||
|
enabled := true
|
||||||
|
originFilters = append(originFilters, model.Filter{Url: url, Enabled: enabled})
|
||||||
|
replicaFilters = append(replicaFilters, model.Filter{Url: url, Enabled: !enabled})
|
||||||
|
a, u, d := model.MergeFilters(&replicaFilters, &originFilters)
|
||||||
|
Ω(a).Should(BeEmpty())
|
||||||
|
Ω(u).Should(HaveLen(1))
|
||||||
|
Ω(d).Should(BeEmpty())
|
||||||
|
|
||||||
|
Ω(u[0].Enabled).Should(Equal(enabled))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should update existing filter when name differs", func() {
|
||||||
|
name1 := uuid.NewString()
|
||||||
|
name2 := uuid.NewString()
|
||||||
|
originFilters = append(originFilters, model.Filter{Url: url, Name: name1})
|
||||||
|
replicaFilters = append(replicaFilters, model.Filter{Url: url, Name: name2})
|
||||||
|
a, u, d := model.MergeFilters(&replicaFilters, &originFilters)
|
||||||
|
Ω(a).Should(BeEmpty())
|
||||||
|
Ω(u).Should(HaveLen(1))
|
||||||
|
Ω(d).Should(BeEmpty())
|
||||||
|
|
||||||
|
Ω(u[0].Name).Should(Equal(name1))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should have no changes", func() {
|
||||||
|
originFilters = append(originFilters, model.Filter{Url: url})
|
||||||
|
replicaFilters = append(replicaFilters, model.Filter{Url: url})
|
||||||
|
a, u, d := model.MergeFilters(&replicaFilters, &originFilters)
|
||||||
|
Ω(a).Should(BeEmpty())
|
||||||
|
Ω(u).Should(BeEmpty())
|
||||||
|
Ω(d).Should(BeEmpty())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("AdGuardInstance", func() {
|
||||||
|
It("should build a key with url and api apiPath", func() {
|
||||||
|
i := &types.AdGuardInstance{URL: url, APIPath: apiPath}
|
||||||
|
Ω(i.Key()).Should(Equal(url + "#" + apiPath))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("RewriteEntry", func() {
|
||||||
|
It("should build a key with url and api apiPath", func() {
|
||||||
|
domain := uuid.NewString()
|
||||||
|
answer := uuid.NewString()
|
||||||
|
re := &model.RewriteEntry{Domain: utils.Ptr(domain), Answer: utils.Ptr(answer)}
|
||||||
|
Ω(re.Key()).Should(Equal(domain + "#" + answer))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("QueryLogConfig", func() {
|
||||||
|
Context("Equal", func() {
|
||||||
|
var (
|
||||||
|
a *model.QueryLogConfigWithIgnored
|
||||||
|
b *model.QueryLogConfigWithIgnored
|
||||||
|
)
|
||||||
|
BeforeEach(func() {
|
||||||
|
a = &model.QueryLogConfigWithIgnored{}
|
||||||
|
b = &model.QueryLogConfigWithIgnored{}
|
||||||
|
})
|
||||||
|
It("should be equal", func() {
|
||||||
|
a.Enabled = utils.Ptr(true)
|
||||||
|
var interval model.QueryLogConfigInterval = 1
|
||||||
|
a.Interval = &interval
|
||||||
|
a.AnonymizeClientIp = utils.Ptr(true)
|
||||||
|
b.Enabled = utils.Ptr(true)
|
||||||
|
b.Interval = &interval
|
||||||
|
b.AnonymizeClientIp = utils.Ptr(true)
|
||||||
|
Ω(a.Equals(b)).Should(BeTrue())
|
||||||
|
})
|
||||||
|
It("should not be equal when enabled differs", func() {
|
||||||
|
a.Enabled = utils.Ptr(true)
|
||||||
|
b.Enabled = utils.Ptr(false)
|
||||||
|
Ω(a.Equals(b)).ShouldNot(BeTrue())
|
||||||
|
})
|
||||||
|
It("should not be equal when interval differs", func() {
|
||||||
|
var interval1 model.QueryLogConfigInterval = 1
|
||||||
|
var interval2 model.QueryLogConfigInterval = 2
|
||||||
|
a.Interval = &interval1
|
||||||
|
b.Interval = &interval2
|
||||||
|
Ω(a.Equals(b)).ShouldNot(BeTrue())
|
||||||
|
})
|
||||||
|
It("should not be equal when anonymizeClientIP differs", func() {
|
||||||
|
a.AnonymizeClientIp = utils.Ptr(true)
|
||||||
|
b.AnonymizeClientIp = utils.Ptr(false)
|
||||||
|
Ω(a.Equals(b)).ShouldNot(BeTrue())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("RewriteEntries", func() {
|
||||||
|
Context("Merge", func() {
|
||||||
|
var (
|
||||||
|
originRE model.RewriteEntries
|
||||||
|
replicaRE model.RewriteEntries
|
||||||
|
domain string
|
||||||
|
)
|
||||||
|
BeforeEach(func() {
|
||||||
|
originRE = model.RewriteEntries{}
|
||||||
|
replicaRE = model.RewriteEntries{}
|
||||||
|
domain = uuid.NewString()
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should add a missing rewrite entry", func() {
|
||||||
|
originRE = append(originRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
|
||||||
|
a, r, d := replicaRE.Merge(&originRE)
|
||||||
|
Ω(a).Should(HaveLen(1))
|
||||||
|
Ω(r).Should(BeEmpty())
|
||||||
|
Ω(d).Should(BeEmpty())
|
||||||
|
|
||||||
|
Ω(*a[0].Domain).Should(Equal(domain))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should remove additional rewrite entry", func() {
|
||||||
|
replicaRE = append(replicaRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
|
||||||
|
a, r, d := replicaRE.Merge(&originRE)
|
||||||
|
Ω(a).Should(BeEmpty())
|
||||||
|
Ω(r).Should(HaveLen(1))
|
||||||
|
Ω(d).Should(BeEmpty())
|
||||||
|
|
||||||
|
Ω(*r[0].Domain).Should(Equal(domain))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should have no changes", func() {
|
||||||
|
originRE = append(originRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
|
||||||
|
replicaRE = append(replicaRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
|
||||||
|
a, r, d := replicaRE.Merge(&originRE)
|
||||||
|
Ω(a).Should(BeEmpty())
|
||||||
|
Ω(r).Should(BeEmpty())
|
||||||
|
Ω(d).Should(BeEmpty())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should remove target duplicate", func() {
|
||||||
|
originRE = append(originRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
|
||||||
|
replicaRE = append(replicaRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
|
||||||
|
replicaRE = append(replicaRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
|
||||||
|
a, r, d := replicaRE.Merge(&originRE)
|
||||||
|
Ω(a).Should(BeEmpty())
|
||||||
|
Ω(r).Should(HaveLen(1))
|
||||||
|
Ω(d).Should(BeEmpty())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should remove target duplicate", func() {
|
||||||
|
originRE = append(originRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
|
||||||
|
originRE = append(originRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
|
||||||
|
replicaRE = append(replicaRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
|
||||||
|
a, r, d := replicaRE.Merge(&originRE)
|
||||||
|
Ω(a).Should(BeEmpty())
|
||||||
|
Ω(r).Should(BeEmpty())
|
||||||
|
Ω(d).Should(HaveLen(1))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("Config", func() {
|
||||||
|
var cfg *types.Config
|
||||||
|
BeforeEach(func() {
|
||||||
|
cfg = &types.Config{}
|
||||||
|
})
|
||||||
|
Context("UniqueReplicas", func() {
|
||||||
|
It("should be empty if noting defined", func() {
|
||||||
|
r := cfg.UniqueReplicas()
|
||||||
|
Ω(r).Should(BeEmpty())
|
||||||
|
})
|
||||||
|
It("should be empty if replica url is not set", func() {
|
||||||
|
cfg.Replica = &types.AdGuardInstance{URL: ""}
|
||||||
|
r := cfg.UniqueReplicas()
|
||||||
|
Ω(r).Should(BeEmpty())
|
||||||
|
})
|
||||||
|
It("should be empty if replicas url is not set", func() {
|
||||||
|
cfg.Replicas = []types.AdGuardInstance{{URL: ""}}
|
||||||
|
r := cfg.UniqueReplicas()
|
||||||
|
Ω(r).Should(BeEmpty())
|
||||||
|
})
|
||||||
|
It("should return only one replica if same url and apiPath", func() {
|
||||||
|
cfg.Replica = &types.AdGuardInstance{URL: url, APIPath: apiPath}
|
||||||
|
cfg.Replicas = []types.AdGuardInstance{{URL: url, APIPath: apiPath}, {URL: url, APIPath: apiPath}}
|
||||||
|
r := cfg.UniqueReplicas()
|
||||||
|
Ω(r).Should(HaveLen(1))
|
||||||
|
})
|
||||||
|
It("should return 3 one replicas if urls are different", func() {
|
||||||
|
cfg.Replica = &types.AdGuardInstance{URL: url, APIPath: apiPath}
|
||||||
|
cfg.Replicas = []types.AdGuardInstance{
|
||||||
|
{URL: url + "1", APIPath: apiPath},
|
||||||
|
{URL: url, APIPath: apiPath + "1"},
|
||||||
|
}
|
||||||
|
r := cfg.UniqueReplicas()
|
||||||
|
Ω(r).Should(HaveLen(3))
|
||||||
|
})
|
||||||
|
It("should set default api apiPath if not set", func() {
|
||||||
|
cfg.Replica = &types.AdGuardInstance{URL: url}
|
||||||
|
cfg.Replicas = []types.AdGuardInstance{{URL: url + "1"}}
|
||||||
|
r := cfg.UniqueReplicas()
|
||||||
|
Ω(r).Should(HaveLen(2))
|
||||||
|
Ω(r[0].APIPath).Should(Equal(types.DefaultAPIPath))
|
||||||
|
Ω(r[1].APIPath).Should(Equal(types.DefaultAPIPath))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("Clients", func() {
|
||||||
|
Context("Merge", func() {
|
||||||
|
var (
|
||||||
|
originClients *model.Clients
|
||||||
|
replicaClients model.Clients
|
||||||
|
name string
|
||||||
|
)
|
||||||
|
BeforeEach(func() {
|
||||||
|
originClients = &model.Clients{}
|
||||||
|
replicaClients = model.Clients{}
|
||||||
|
name = uuid.NewString()
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should add a missing client", func() {
|
||||||
|
originClients.Add(model.Client{Name: utils.Ptr(name)})
|
||||||
|
a, u, d := replicaClients.Merge(originClients)
|
||||||
|
Ω(a).Should(HaveLen(1))
|
||||||
|
Ω(u).Should(BeEmpty())
|
||||||
|
Ω(d).Should(BeEmpty())
|
||||||
|
|
||||||
|
Ω(*a[0].Name).Should(Equal(name))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should remove additional client", func() {
|
||||||
|
replicaClients.Add(model.Client{Name: utils.Ptr(name)})
|
||||||
|
a, u, d := replicaClients.Merge(originClients)
|
||||||
|
Ω(a).Should(BeEmpty())
|
||||||
|
Ω(u).Should(BeEmpty())
|
||||||
|
Ω(d).Should(HaveLen(1))
|
||||||
|
|
||||||
|
Ω(*d[0].Name).Should(Equal(name))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should update existing client when name differs", func() {
|
||||||
|
disallowed := true
|
||||||
|
originClients.Add(model.Client{Name: utils.Ptr(name), FilteringEnabled: utils.Ptr(disallowed)})
|
||||||
|
replicaClients.Add(model.Client{Name: utils.Ptr(name), FilteringEnabled: utils.Ptr(!disallowed)})
|
||||||
|
a, u, d := replicaClients.Merge(originClients)
|
||||||
|
Ω(a).Should(BeEmpty())
|
||||||
|
Ω(u).Should(HaveLen(1))
|
||||||
|
Ω(d).Should(BeEmpty())
|
||||||
|
|
||||||
|
Ω(*u[0].FilteringEnabled).Should(Equal(disallowed))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("Client", func() {
|
||||||
|
Context("Equals", func() {
|
||||||
|
var (
|
||||||
|
cl1 *model.Client
|
||||||
|
cl2 *model.Client
|
||||||
|
)
|
||||||
|
BeforeEach(func() {
|
||||||
|
cl1 = &model.Client{
|
||||||
|
Name: utils.Ptr("foo"),
|
||||||
|
BlockedServicesSchedule: &model.Schedule{TimeZone: utils.Ptr("UTC")},
|
||||||
|
}
|
||||||
|
cl2 = &model.Client{
|
||||||
|
Name: utils.Ptr("foo"),
|
||||||
|
BlockedServicesSchedule: &model.Schedule{TimeZone: utils.Ptr("Local")},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should equal if only timezone differs on empty blocked service schedule", func() {
|
||||||
|
Ω(cl1.Equals(cl2)).Should(BeTrue())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("BlockedServices", func() {
|
||||||
|
Context("Equals", func() {
|
||||||
|
It("should be equal", func() {
|
||||||
|
s1 := &model.BlockedServicesArray{"a", "b"}
|
||||||
|
s2 := &model.BlockedServicesArray{"b", "a"}
|
||||||
|
Ω(model.EqualsStringSlice(s1, s2, true)).Should(BeTrue())
|
||||||
|
})
|
||||||
|
It("should not be equal different values", func() {
|
||||||
|
s1 := &model.BlockedServicesArray{"a", "b"}
|
||||||
|
s2 := &model.BlockedServicesArray{"B", "a"}
|
||||||
|
Ω(model.EqualsStringSlice(s1, s2, true)).ShouldNot(BeTrue())
|
||||||
|
})
|
||||||
|
It("should not be equal different length", func() {
|
||||||
|
s1 := &model.BlockedServicesArray{"a", "b"}
|
||||||
|
s2 := &model.BlockedServicesArray{"b", "a", "c"}
|
||||||
|
Ω(model.EqualsStringSlice(s1, s2, true)).ShouldNot(BeTrue())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("DNSConfig", func() {
|
||||||
|
Context("Equals", func() {
|
||||||
|
It("should be equal", func() {
|
||||||
|
dc1 := &model.DNSConfig{LocalPtrUpstreams: utils.Ptr([]string{"a"})}
|
||||||
|
dc2 := &model.DNSConfig{LocalPtrUpstreams: utils.Ptr([]string{"a"})}
|
||||||
|
Ω(dc1.Equals(dc2)).Should(BeTrue())
|
||||||
|
})
|
||||||
|
It("should not be equal", func() {
|
||||||
|
dc1 := &model.DNSConfig{LocalPtrUpstreams: utils.Ptr([]string{"a"})}
|
||||||
|
dc2 := &model.DNSConfig{LocalPtrUpstreams: utils.Ptr([]string{"b"})}
|
||||||
|
Ω(dc1.Equals(dc2)).ShouldNot(BeTrue())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("DHCPServerConfig", func() {
|
||||||
|
Context("Equals", func() {
|
||||||
|
It("should be equal", func() {
|
||||||
|
dc1 := &model.DhcpStatus{
|
||||||
|
V4: &model.DhcpConfigV4{
|
||||||
|
GatewayIp: utils.Ptr("1.2.3.4"),
|
||||||
|
LeaseDuration: utils.Ptr(123),
|
||||||
|
RangeStart: utils.Ptr("1.2.3.5"),
|
||||||
|
RangeEnd: utils.Ptr("1.2.3.6"),
|
||||||
|
SubnetMask: utils.Ptr("255.255.255.0"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
dc2 := &model.DhcpStatus{
|
||||||
|
V4: &model.DhcpConfigV4{
|
||||||
|
GatewayIp: utils.Ptr("1.2.3.4"),
|
||||||
|
LeaseDuration: utils.Ptr(123),
|
||||||
|
RangeStart: utils.Ptr("1.2.3.5"),
|
||||||
|
RangeEnd: utils.Ptr("1.2.3.6"),
|
||||||
|
SubnetMask: utils.Ptr("255.255.255.0"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Ω(dc1.Equals(dc2)).Should(BeTrue())
|
||||||
|
})
|
||||||
|
It("should not be equal", func() {
|
||||||
|
dc1 := &model.DhcpStatus{
|
||||||
|
V4: &model.DhcpConfigV4{
|
||||||
|
GatewayIp: utils.Ptr("1.2.3.3"),
|
||||||
|
LeaseDuration: utils.Ptr(123),
|
||||||
|
RangeStart: utils.Ptr("1.2.3.5"),
|
||||||
|
RangeEnd: utils.Ptr("1.2.3.6"),
|
||||||
|
SubnetMask: utils.Ptr("255.255.255.0"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
dc2 := &model.DhcpStatus{
|
||||||
|
V4: &model.DhcpConfigV4{
|
||||||
|
GatewayIp: utils.Ptr("1.2.3.4"),
|
||||||
|
LeaseDuration: utils.Ptr(123),
|
||||||
|
RangeStart: utils.Ptr("1.2.3.5"),
|
||||||
|
RangeEnd: utils.Ptr("1.2.3.6"),
|
||||||
|
SubnetMask: utils.Ptr("255.255.255.0"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Ω(dc1.Equals(dc2)).ShouldNot(BeTrue())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("Clone", func() {
|
||||||
|
It("clone should be equal", func() {
|
||||||
|
dc1 := &model.DhcpStatus{
|
||||||
|
V4: &model.DhcpConfigV4{
|
||||||
|
GatewayIp: utils.Ptr("1.2.3.4"),
|
||||||
|
LeaseDuration: utils.Ptr(123),
|
||||||
|
RangeStart: utils.Ptr("1.2.3.5"),
|
||||||
|
RangeEnd: utils.Ptr("1.2.3.6"),
|
||||||
|
SubnetMask: utils.Ptr("255.255.255.0"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Ω(dc1.Clone().Equals(dc1)).Should(BeTrue())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("HasConfig", func() {
|
||||||
|
It("should not have a config", func() {
|
||||||
|
dc1 := &model.DhcpStatus{
|
||||||
|
V4: &model.DhcpConfigV4{},
|
||||||
|
V6: &model.DhcpConfigV6{},
|
||||||
|
}
|
||||||
|
Ω(dc1.HasConfig()).Should(BeFalse())
|
||||||
|
})
|
||||||
|
It("should not have a v4 config with nil IP", func() {
|
||||||
|
dc1 := &model.DhcpStatus{
|
||||||
|
V4: &model.DhcpConfigV4{
|
||||||
|
GatewayIp: nil,
|
||||||
|
},
|
||||||
|
V6: &model.DhcpConfigV6{
|
||||||
|
RangeStart: utils.Ptr("1.2.3.5"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Ω(dc1.HasConfig()).Should(BeTrue())
|
||||||
|
})
|
||||||
|
It("should not have a v4 config with empty IP", func() {
|
||||||
|
dc1 := &model.DhcpStatus{
|
||||||
|
V4: &model.DhcpConfigV4{
|
||||||
|
GatewayIp: utils.Ptr(""),
|
||||||
|
},
|
||||||
|
V6: &model.DhcpConfigV6{
|
||||||
|
RangeStart: utils.Ptr("1.2.3.5"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Ω(dc1.HasConfig()).Should(BeTrue())
|
||||||
|
})
|
||||||
|
It("should not have a v6 config", func() {
|
||||||
|
dc1 := &model.DhcpStatus{
|
||||||
|
V4: &model.DhcpConfigV4{
|
||||||
|
GatewayIp: utils.Ptr("1.2.3.4"),
|
||||||
|
LeaseDuration: utils.Ptr(123),
|
||||||
|
RangeStart: utils.Ptr("1.2.3.5"),
|
||||||
|
RangeEnd: utils.Ptr("1.2.3.6"),
|
||||||
|
SubnetMask: utils.Ptr("255.255.255.0"),
|
||||||
|
},
|
||||||
|
V6: &model.DhcpConfigV6{},
|
||||||
|
}
|
||||||
|
Ω(dc1.HasConfig()).Should(BeTrue())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
181
internal/config/config-schema.json
Normal file
181
internal/config/config-schema.json
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"definitions": {
|
||||||
|
"Instance": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"apiPath": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"autoSetup": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"cookie": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"dhcpServerEnabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"insecureSkipVerify": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"interfaceName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"format": "uri",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"webURL": {
|
||||||
|
"format": "uri",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"requestHeaders": {
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "validates only for valid schema. No required fields, as the can be defined via env ars afterwards.",
|
||||||
|
"properties": {
|
||||||
|
"api": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"darkMode": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"metrics": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"queryLogLimit": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"scrapeInterval": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"port": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"tls": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"certDir": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"certName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"keyName": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"continueOnError": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"cron": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"clientSettings": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"dhcp": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"serverConfig": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"staticLeases": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"dns": {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"accessLists": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"rewrites": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"serverConfig": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"filters": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"generalSettings": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"queryLogConfig": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"statsConfig": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"tlsConfig": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"origin": {
|
||||||
|
"$ref": "#/definitions/Instance"
|
||||||
|
},
|
||||||
|
"printConfigOnly": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"replica": {
|
||||||
|
"$ref": "#/definitions/Instance"
|
||||||
|
},
|
||||||
|
"replicas": {
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/Instance"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"runOnStart": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "adguardhome-sync Configuration",
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
129
internal/config/config.go
Normal file
129
internal/config/config.go
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/caarlos0/env/v11"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/log"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
envReplicasURLPattern = regexp.MustCompile(`^REPLICA(\d+)_URL=(.*)`)
|
||||||
|
logger = log.GetLogger("config")
|
||||||
|
)
|
||||||
|
|
||||||
|
type AppConfig struct {
|
||||||
|
cfg *types.Config
|
||||||
|
filePath string
|
||||||
|
content string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *AppConfig) PrintConfigOnly() bool {
|
||||||
|
return ac.cfg.PrintConfigOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *AppConfig) Get() *types.Config {
|
||||||
|
return ac.cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *AppConfig) Init() error {
|
||||||
|
return ac.cfg.Init()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(configFile string, flags Flags) (*AppConfig, error) {
|
||||||
|
path, err := configFilePath(configFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateSchema(path); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := initialConfig()
|
||||||
|
|
||||||
|
// read yaml config
|
||||||
|
var content string
|
||||||
|
if content, err = readFile(cfg, path); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// overwrite from command flags
|
||||||
|
if err := readFlags(cfg, flags); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// *bool field creates issues when already not nil
|
||||||
|
cfg.Origin.DHCPServerEnabled = nil // origin filed makes no sense to be set.
|
||||||
|
|
||||||
|
// keep previously set value
|
||||||
|
replicaDhcpServer := cfg.Replica.DHCPServerEnabled
|
||||||
|
cfg.Replica.DHCPServerEnabled = nil
|
||||||
|
|
||||||
|
// ignore origin and replicas form env parsing as they are handled separately
|
||||||
|
replicas := cfg.Replicas
|
||||||
|
cfg.Replicas = nil
|
||||||
|
replica := cfg.Replica
|
||||||
|
cfg.Replica = nil
|
||||||
|
origin := cfg.Origin
|
||||||
|
cfg.Origin = nil
|
||||||
|
|
||||||
|
// overwrite from env vars
|
||||||
|
if err := env.Parse(cfg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := env.ParseWithOptions(origin, env.Options{Prefix: "ORIGIN_"}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := env.ParseWithOptions(replica, env.Options{Prefix: "REPLICA_"}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// restore origin and replica
|
||||||
|
cfg.Origin = origin
|
||||||
|
cfg.Replica = replica
|
||||||
|
cfg.Replicas = replicas
|
||||||
|
|
||||||
|
// if not set from env, use previous value
|
||||||
|
if cfg.Replica.DHCPServerEnabled == nil {
|
||||||
|
cfg.Replica.DHCPServerEnabled = replicaDhcpServer
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Replica != nil &&
|
||||||
|
cfg.Replica.URL == "" &&
|
||||||
|
cfg.Replica.Username == "" {
|
||||||
|
cfg.Replica = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.Replicas) > 0 && cfg.Replica != nil {
|
||||||
|
return nil, errors.New("mixed replica config in use. " +
|
||||||
|
"Do not use single replica and numbered (list) replica config combined")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Replica != nil {
|
||||||
|
cfg.Replicas = []types.AdGuardInstance{*cfg.Replica}
|
||||||
|
cfg.Replica = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Replicas, err = enrichReplicasFromEnv(cfg.Replicas)
|
||||||
|
|
||||||
|
return &AppConfig{cfg: cfg, filePath: path, content: content}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func initialConfig() *types.Config {
|
||||||
|
return &types.Config{
|
||||||
|
RunOnStart: true,
|
||||||
|
Origin: &types.AdGuardInstance{
|
||||||
|
APIPath: "/control",
|
||||||
|
},
|
||||||
|
Replica: &types.AdGuardInstance{
|
||||||
|
APIPath: "/control",
|
||||||
|
},
|
||||||
|
API: types.API{
|
||||||
|
Port: 8080,
|
||||||
|
},
|
||||||
|
Features: types.NewFeatures(true),
|
||||||
|
}
|
||||||
|
}
|
||||||
15
internal/config/config_suite_test.go
Normal file
15
internal/config/config_suite_test.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package config_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/onsi/gomega/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCmd(t *testing.T) {
|
||||||
|
format.TruncatedDiff = false
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Config Suite")
|
||||||
|
}
|
||||||
260
internal/config/config_test.go
Normal file
260
internal/config/config_test.go
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
package config_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
gm "go.uber.org/mock/gomock"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/config"
|
||||||
|
flagsmock "github.com/bakito/adguardhome-sync/internal/mocks/flags"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Config", func() {
|
||||||
|
Context("Get", func() {
|
||||||
|
var (
|
||||||
|
flags *flagsmock.MockFlags
|
||||||
|
mockCtrl *gm.Controller
|
||||||
|
changedEnvVars []string
|
||||||
|
setEnv = func(name, value string) {
|
||||||
|
_ = os.Setenv(name, value)
|
||||||
|
changedEnvVars = append(changedEnvVars, name)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
BeforeEach(func() {
|
||||||
|
mockCtrl = gm.NewController(GinkgoT())
|
||||||
|
flags = flagsmock.NewMockFlags(mockCtrl)
|
||||||
|
changedEnvVars = nil
|
||||||
|
})
|
||||||
|
AfterEach(func() {
|
||||||
|
for _, envVar := range changedEnvVars {
|
||||||
|
_ = os.Unsetenv(envVar)
|
||||||
|
println(envVar)
|
||||||
|
}
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
})
|
||||||
|
Context("Get", func() {
|
||||||
|
Context("Mixed Config", func() {
|
||||||
|
It("should have the origin URL from the config file", func() {
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
|
||||||
|
_, err := config.Get("../../testdata/config_test_replicas_and_replica.yaml", flags)
|
||||||
|
Ω(err).Should(HaveOccurred())
|
||||||
|
Ω(err.Error()).Should(ContainSubstring("mixed replica config in use"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("Env Var Clash", func() {
|
||||||
|
It("should not use USERNAME env variable if it is defined (#570)", func() {
|
||||||
|
incorrect := "ThisIsNotTheCorrectUsername"
|
||||||
|
setEnv("USERNAME", incorrect)
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
|
||||||
|
c, err := config.Get("../../testdata/config_test_replica.yaml", flags)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(c.Get().Origin.Username).ShouldNot(Equal(incorrect))
|
||||||
|
Ω(c.Get().Replicas[0].Username).ShouldNot(Equal(incorrect))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("Origin Url", func() {
|
||||||
|
It("should have the origin URL from the config file", func() {
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
|
||||||
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Get().Origin.URL).Should(Equal("https://origin-file:443"))
|
||||||
|
})
|
||||||
|
It("should have the origin URL from the config flags", func() {
|
||||||
|
flags.EXPECT().Changed(config.FlagOriginURL).Return(true).AnyTimes()
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
flags.EXPECT().GetString(config.FlagOriginURL).Return("https://origin-flag:443", nil).AnyTimes()
|
||||||
|
|
||||||
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Get().Origin.URL).Should(Equal("https://origin-flag:443"))
|
||||||
|
})
|
||||||
|
It("should have the origin URL from the config env var", func() {
|
||||||
|
setEnv("ORIGIN_URL", "https://origin-env:443")
|
||||||
|
flags.EXPECT().Changed(config.FlagOriginURL).Return(true).AnyTimes()
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
flags.EXPECT().GetString(config.FlagOriginURL).Return("https://origin-flag:443", nil).AnyTimes()
|
||||||
|
|
||||||
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Get().Origin.URL).Should(Equal("https://origin-env:443"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("Replica insecure skip verify", func() {
|
||||||
|
It("should have the insecure skip verify from the config file", func() {
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
|
||||||
|
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeFalse())
|
||||||
|
})
|
||||||
|
It("should have the insecure skip verify from the config flags", func() {
|
||||||
|
flags.EXPECT().Changed(config.FlagReplicaISV).Return(true).AnyTimes()
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
flags.EXPECT().GetBool(config.FlagReplicaISV).Return(true, nil).AnyTimes()
|
||||||
|
|
||||||
|
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeTrue())
|
||||||
|
})
|
||||||
|
It("should have the insecure skip verify from the config env var", func() {
|
||||||
|
setEnv("REPLICA_INSECURE_SKIP_VERIFY", "false")
|
||||||
|
flags.EXPECT().Changed(config.FlagReplicaISV).Return(true).AnyTimes()
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
flags.EXPECT().GetBool(config.FlagReplicaISV).Return(true, nil).AnyTimes()
|
||||||
|
|
||||||
|
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeFalse())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("Replica 1 insecure skip verify", func() {
|
||||||
|
It("should have the insecure skip verify from the config file", func() {
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
|
||||||
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeFalse())
|
||||||
|
})
|
||||||
|
It("should have the insecure skip verify from the config env var", func() {
|
||||||
|
setEnv("REPLICA1_INSECURE_SKIP_VERIFY", "true")
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
|
||||||
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeTrue())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("API Port", func() {
|
||||||
|
It("should have the api port from the config file", func() {
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Get().API.Port).Should(Equal(9090))
|
||||||
|
})
|
||||||
|
It("should have the api port from the config flags", func() {
|
||||||
|
flags.EXPECT().Changed(config.FlagAPIPort).Return(true).AnyTimes()
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
flags.EXPECT().GetInt(config.FlagAPIPort).Return(9990, nil).AnyTimes()
|
||||||
|
|
||||||
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Get().API.Port).Should(Equal(9990))
|
||||||
|
})
|
||||||
|
It("should have the api port from the config env var", func() {
|
||||||
|
setEnv("API_PORT", "9999")
|
||||||
|
flags.EXPECT().Changed(config.FlagAPIPort).Return(true).AnyTimes()
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
flags.EXPECT().GetInt(config.FlagAPIPort).Return(9990, nil).AnyTimes()
|
||||||
|
|
||||||
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Get().API.Port).Should(Equal(9999))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("Replica DHCPServerEnabled", func() {
|
||||||
|
It("should have the dhcp server enabled from the config file", func() {
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
|
||||||
|
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Get().Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
|
||||||
|
Ω(*cfg.Get().Replicas[0].DHCPServerEnabled).Should(BeFalse())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("Replica 1 DHCPServerEnabled", func() {
|
||||||
|
It("should have the dhcp server enabled from the config file", func() {
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
|
||||||
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Get().Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
|
||||||
|
Ω(*cfg.Get().Replicas[0].DHCPServerEnabled).Should(BeFalse())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("API Port", func() {
|
||||||
|
It("should have the api port from the config file", func() {
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Get().API.Port).Should(Equal(9090))
|
||||||
|
})
|
||||||
|
It("should have the api port from the config flags", func() {
|
||||||
|
flags.EXPECT().Changed(config.FlagAPIPort).Return(true).AnyTimes()
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
flags.EXPECT().GetInt(config.FlagAPIPort).Return(9990, nil).AnyTimes()
|
||||||
|
|
||||||
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Get().API.Port).Should(Equal(9990))
|
||||||
|
})
|
||||||
|
It("should have the api port from the config env var", func() {
|
||||||
|
setEnv("API_PORT", "9999")
|
||||||
|
flags.EXPECT().Changed(config.FlagAPIPort).Return(true).AnyTimes()
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
flags.EXPECT().GetInt(config.FlagAPIPort).Return(9990, nil).AnyTimes()
|
||||||
|
|
||||||
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Get().API.Port).Should(Equal(9999))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("Feature DNS Server Config", func() {
|
||||||
|
It("should have the feature dns server config from the config file", func() {
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Get().Features.DNS.ServerConfig).Should(BeFalse())
|
||||||
|
})
|
||||||
|
It("should have the feature dns server config from the config flags", func() {
|
||||||
|
flags.EXPECT().Changed(config.FlagFeatureDNSServerConfig).Return(true).AnyTimes()
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
flags.EXPECT().GetBool(config.FlagFeatureDNSServerConfig).Return(true, nil).AnyTimes()
|
||||||
|
|
||||||
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Get().Features.DNS.ServerConfig).Should(BeTrue())
|
||||||
|
})
|
||||||
|
It("should have the feature dns server config from the config env var", func() {
|
||||||
|
setEnv("FEATURES_DNS_SERVER_CONFIG", "false")
|
||||||
|
flags.EXPECT().Changed(config.FlagFeatureDNSServerConfig).Return(true).AnyTimes()
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
flags.EXPECT().GetBool(config.FlagFeatureDNSServerConfig).Return(true, nil).AnyTimes()
|
||||||
|
|
||||||
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Get().Features.DNS.ServerConfig).Should(BeFalse())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("Headers", func() {
|
||||||
|
It("have headers from the config file", func() {
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
|
||||||
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Get().Replicas[0].RequestHeaders).Should(HaveLen(2))
|
||||||
|
Ω(cfg.Get().Replicas[0].RequestHeaders["FOO"]).Should(Equal("bar"))
|
||||||
|
Ω(cfg.Get().Replicas[0].RequestHeaders["Client-ID"]).Should(Equal("xxxx"))
|
||||||
|
})
|
||||||
|
It("have headers from the config file will be replaced when defined as ENV", func() {
|
||||||
|
setEnv("REPLICA1_REQUEST_HEADERS", "AAA:bbb")
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
|
||||||
|
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Get().Replicas[0].RequestHeaders).Should(HaveLen(1))
|
||||||
|
Ω(cfg.Get().Replicas[0].RequestHeaders["AAA"]).Should(Equal("bbb"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
57
internal/config/env.go
Normal file
57
internal/config/env.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/caarlos0/env/v11"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Manually collect replicas from env.
|
||||||
|
func enrichReplicasFromEnv(initialReplicas []types.AdGuardInstance) ([]types.AdGuardInstance, error) {
|
||||||
|
var replicas []types.AdGuardInstance
|
||||||
|
for _, v := range os.Environ() {
|
||||||
|
if envReplicasURLPattern.MatchString(v) {
|
||||||
|
sm := envReplicasURLPattern.FindStringSubmatch(v)
|
||||||
|
id, _ := strconv.Atoi(sm[1])
|
||||||
|
|
||||||
|
if id <= 0 {
|
||||||
|
return nil, fmt.Errorf("numbered replica env variables must have a number id >= 1, got %q", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if id > len(initialReplicas) {
|
||||||
|
replicas = append(replicas, types.AdGuardInstance{URL: sm[2]})
|
||||||
|
} else {
|
||||||
|
re := initialReplicas[id-1]
|
||||||
|
re.URL = sm[2]
|
||||||
|
replicas = append(replicas, re)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(replicas) == 0 {
|
||||||
|
replicas = initialReplicas
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range replicas {
|
||||||
|
reID := i + 1
|
||||||
|
|
||||||
|
// keep the previously set value
|
||||||
|
replicaDhcpServer := replicas[i].DHCPServerEnabled
|
||||||
|
replicas[i].DHCPServerEnabled = nil
|
||||||
|
if err := env.ParseWithOptions(&replicas[i], env.Options{Prefix: fmt.Sprintf("REPLICA%d_", reID)}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if replicas[i].DHCPServerEnabled == nil {
|
||||||
|
replicas[i].DHCPServerEnabled = replicaDhcpServer
|
||||||
|
}
|
||||||
|
if replicas[i].APIPath == "" {
|
||||||
|
replicas[i].APIPath = "/control"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return replicas, nil
|
||||||
|
}
|
||||||
25
internal/config/env_test.go
Normal file
25
internal/config/env_test.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Config", func() {
|
||||||
|
Context("env", func() {
|
||||||
|
Context("enrichReplicasFromEnv", func() {
|
||||||
|
It("should have the origin URL from the config env var", func() {
|
||||||
|
_ = os.Setenv("REPLICA0_URL", "https://origin-env:443")
|
||||||
|
defer func() {
|
||||||
|
_ = os.Unsetenv("REPLICA0_URL")
|
||||||
|
}()
|
||||||
|
_, err := enrichReplicasFromEnv(nil)
|
||||||
|
|
||||||
|
Ω(err).Should(HaveOccurred())
|
||||||
|
Ω(err.Error()).Should(ContainSubstring("numbered replica env variables must have a number id >= 1"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
37
internal/config/file.go
Normal file
37
internal/config/file.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func readFile(cfg *types.Config, path string) (string, error) {
|
||||||
|
var content string
|
||||||
|
if _, err := os.Stat(path); err == nil {
|
||||||
|
b, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
content = string(b)
|
||||||
|
if err := yaml.Unmarshal(b, cfg); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return content, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func configFilePath(configFile string) (string, error) {
|
||||||
|
if configFile == "" {
|
||||||
|
// Find home directory.
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return filepath.Join(home, ".adguardhome-sync.yaml"), nil
|
||||||
|
}
|
||||||
|
return configFile, nil
|
||||||
|
}
|
||||||
30
internal/config/file_test.go
Normal file
30
internal/config/file_test.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Config", func() {
|
||||||
|
Context("configFilePath", func() {
|
||||||
|
It("should return the same value", func() {
|
||||||
|
path := uuid.NewString()
|
||||||
|
result, err := configFilePath(path)
|
||||||
|
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(result).Should(Equal(path))
|
||||||
|
})
|
||||||
|
It("should the file in HOME dir", func() {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
result, err := configFilePath("")
|
||||||
|
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(result).Should(Equal(filepath.Join(home, ".adguardhome-sync.yaml")))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
45
internal/config/flag-names.go
Normal file
45
internal/config/flag-names.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
const (
|
||||||
|
FlagCron = "cron"
|
||||||
|
FlagRunOnStart = "runOnStart"
|
||||||
|
FlagPrintConfigOnly = "printConfigOnly"
|
||||||
|
FlagContinueOnError = "continueOnError"
|
||||||
|
|
||||||
|
FlagAPIPort = "api-port"
|
||||||
|
FlagAPIUsername = "api-username"
|
||||||
|
FlagAPIPassword = "api-password"
|
||||||
|
FlagAPIDarkMode = "api-dark-mode"
|
||||||
|
|
||||||
|
FlagFeatureDhcpServerConfig = "feature-dhcp-server-config"
|
||||||
|
FlagFeatureDhcpStaticLeases = "feature-dhcp-static-leases"
|
||||||
|
FlagFeatureDNSServerConfig = "feature-dns-server-config"
|
||||||
|
FlagFeatureDNSAccessLists = "feature-dns-access-lists"
|
||||||
|
FlagFeatureDNSRewrites = "feature-dns-rewrites"
|
||||||
|
FlagFeatureGeneral = "feature-general-settings"
|
||||||
|
FlagFeatureQueryLog = "feature-query-log-config"
|
||||||
|
FlagFeatureStats = "feature-stats-config"
|
||||||
|
FlagFeatureClient = "feature-client-settings"
|
||||||
|
FlagFeatureServices = "feature-services"
|
||||||
|
FlagFeatureFilters = "feature-filters"
|
||||||
|
FlagFeatureTLSConfig = "feature-tls-config"
|
||||||
|
|
||||||
|
FlagOriginURL = "origin-url"
|
||||||
|
FlagOriginWebURL = "origin-web-url"
|
||||||
|
FlagOriginAPIPath = "origin-api-path"
|
||||||
|
FlagOriginUsername = "origin-username"
|
||||||
|
|
||||||
|
FlagOriginPassword = "origin-password"
|
||||||
|
FlagOriginCookie = "origin-cookie"
|
||||||
|
FlagOriginISV = "origin-insecure-skip-verify"
|
||||||
|
|
||||||
|
FlagReplicaURL = "replica-url"
|
||||||
|
FlagReplicaWebURL = "replica-web-url"
|
||||||
|
FlagReplicaAPIPath = "replica-api-path"
|
||||||
|
FlagReplicaUsername = "replica-username"
|
||||||
|
FlagReplicaPassword = "replica-password"
|
||||||
|
FlagReplicaCookie = "replica-cookie"
|
||||||
|
FlagReplicaISV = "replica-insecure-skip-verify"
|
||||||
|
FlagReplicaAutoSetup = "replica-auto-setup"
|
||||||
|
FlagReplicaInterfaceName = "replica-interface-name"
|
||||||
|
)
|
||||||
268
internal/config/flags.go
Normal file
268
internal/config/flags.go
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func readFlags(cfg *types.Config, flags Flags) error {
|
||||||
|
if flags == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fr := &flagReader{
|
||||||
|
cfg: cfg,
|
||||||
|
flags: flags,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fr.readRootFlags(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fr.readAPIFlags(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fr.readFeatureFlags(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fr.readOriginFlags(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fr.readReplicaFlags()
|
||||||
|
}
|
||||||
|
|
||||||
|
type flagReader struct {
|
||||||
|
cfg *types.Config
|
||||||
|
flags Flags
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fr *flagReader) readReplicaFlags() error {
|
||||||
|
if err := fr.setStringFlag(FlagReplicaURL, func(cgf *types.Config, value string) {
|
||||||
|
fr.cfg.Replica.URL = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fr.setStringFlag(FlagReplicaWebURL, func(cgf *types.Config, value string) {
|
||||||
|
fr.cfg.Replica.WebURL = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fr.setStringFlag(FlagReplicaAPIPath, func(cgf *types.Config, value string) {
|
||||||
|
fr.cfg.Replica.APIPath = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fr.setStringFlag(FlagReplicaUsername, func(cgf *types.Config, value string) {
|
||||||
|
fr.cfg.Replica.Username = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fr.setStringFlag(FlagReplicaPassword, func(cgf *types.Config, value string) {
|
||||||
|
fr.cfg.Replica.Password = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fr.setStringFlag(FlagReplicaCookie, func(cgf *types.Config, value string) {
|
||||||
|
fr.cfg.Replica.Cookie = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fr.setBoolFlag(FlagReplicaISV, func(cgf *types.Config, value bool) {
|
||||||
|
fr.cfg.Replica.InsecureSkipVerify = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fr.setBoolFlag(FlagReplicaAutoSetup, func(cgf *types.Config, value bool) {
|
||||||
|
fr.cfg.Replica.AutoSetup = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fr.setStringFlag(FlagReplicaInterfaceName, func(cgf *types.Config, value string) {
|
||||||
|
fr.cfg.Replica.InterfaceName = value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fr *flagReader) readOriginFlags() error {
|
||||||
|
if err := fr.setStringFlag(FlagOriginURL, func(cgf *types.Config, value string) {
|
||||||
|
fr.cfg.Origin.URL = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fr.setStringFlag(FlagOriginWebURL, func(cgf *types.Config, value string) {
|
||||||
|
fr.cfg.Origin.WebURL = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fr.setStringFlag(FlagOriginAPIPath, func(cgf *types.Config, value string) {
|
||||||
|
fr.cfg.Origin.APIPath = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fr.setStringFlag(FlagOriginUsername, func(cgf *types.Config, value string) {
|
||||||
|
fr.cfg.Origin.Username = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fr.setStringFlag(FlagOriginPassword, func(cgf *types.Config, value string) {
|
||||||
|
fr.cfg.Origin.Password = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fr.setStringFlag(FlagOriginCookie, func(cgf *types.Config, value string) {
|
||||||
|
fr.cfg.Origin.Cookie = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fr.setBoolFlag(FlagOriginISV, func(cgf *types.Config, value bool) {
|
||||||
|
fr.cfg.Origin.InsecureSkipVerify = value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fr *flagReader) readFeatureFlags() error {
|
||||||
|
if err := fr.setBoolFlag(FlagFeatureDhcpServerConfig, func(cgf *types.Config, value bool) {
|
||||||
|
fr.cfg.Features.DHCP.ServerConfig = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fr.setBoolFlag(FlagFeatureDhcpStaticLeases, func(cgf *types.Config, value bool) {
|
||||||
|
fr.cfg.Features.DHCP.StaticLeases = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fr.setBoolFlag(FlagFeatureDNSServerConfig, func(cgf *types.Config, value bool) {
|
||||||
|
fr.cfg.Features.DNS.ServerConfig = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fr.setBoolFlag(FlagFeatureDNSAccessLists, func(cgf *types.Config, value bool) {
|
||||||
|
fr.cfg.Features.DNS.AccessLists = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fr.setBoolFlag(FlagFeatureDNSRewrites, func(cgf *types.Config, value bool) {
|
||||||
|
fr.cfg.Features.DNS.Rewrites = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fr.setBoolFlag(FlagFeatureGeneral, func(cgf *types.Config, value bool) {
|
||||||
|
fr.cfg.Features.GeneralSettings = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fr.setBoolFlag(FlagFeatureQueryLog, func(cgf *types.Config, value bool) {
|
||||||
|
fr.cfg.Features.QueryLogConfig = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fr.setBoolFlag(FlagFeatureStats, func(cgf *types.Config, value bool) {
|
||||||
|
fr.cfg.Features.StatsConfig = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fr.setBoolFlag(FlagFeatureClient, func(cgf *types.Config, value bool) {
|
||||||
|
fr.cfg.Features.ClientSettings = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fr.setBoolFlag(FlagFeatureServices, func(cgf *types.Config, value bool) {
|
||||||
|
fr.cfg.Features.Services = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fr.setBoolFlag(FlagFeatureFilters, func(cgf *types.Config, value bool) {
|
||||||
|
fr.cfg.Features.Filters = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fr.setBoolFlag(FlagFeatureTLSConfig, func(cgf *types.Config, value bool) {
|
||||||
|
fr.cfg.Features.TLSConfig = value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fr *flagReader) readAPIFlags() error {
|
||||||
|
if err := fr.setIntFlag(FlagAPIPort, func(cgf *types.Config, value int) {
|
||||||
|
fr.cfg.API.Port = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fr.setStringFlag(FlagAPIUsername, func(cgf *types.Config, value string) {
|
||||||
|
fr.cfg.API.Username = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fr.setStringFlag(FlagAPIPassword, func(cgf *types.Config, value string) {
|
||||||
|
fr.cfg.API.Password = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fr.setBoolFlag(FlagAPIDarkMode, func(cgf *types.Config, value bool) {
|
||||||
|
fr.cfg.API.DarkMode = value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fr *flagReader) readRootFlags() error {
|
||||||
|
if err := fr.setStringFlag(FlagCron, func(cgf *types.Config, value string) {
|
||||||
|
fr.cfg.Cron = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fr.setBoolFlag(FlagRunOnStart, func(cgf *types.Config, value bool) {
|
||||||
|
fr.cfg.RunOnStart = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fr.setBoolFlag(FlagPrintConfigOnly, func(cgf *types.Config, value bool) {
|
||||||
|
fr.cfg.PrintConfigOnly = value
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fr.setBoolFlag(FlagContinueOnError, func(cgf *types.Config, value bool) {
|
||||||
|
fr.cfg.ContinueOnError = value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Flags interface {
|
||||||
|
Changed(name string) bool
|
||||||
|
GetString(name string) (string, error)
|
||||||
|
GetInt(name string) (int, error)
|
||||||
|
GetBool(name string) (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fr *flagReader) setStringFlag(name string, cb callback[string]) (err error) {
|
||||||
|
if fr.flags.Changed(name) {
|
||||||
|
var value string
|
||||||
|
if value, err = fr.flags.GetString(name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cb(fr.cfg, value)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fr *flagReader) setBoolFlag(name string, cb callback[bool]) (err error) {
|
||||||
|
if fr.flags.Changed(name) {
|
||||||
|
var value bool
|
||||||
|
if value, err = fr.flags.GetBool(name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cb(fr.cfg, value)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fr *flagReader) setIntFlag(name string, cb callback[int]) (err error) {
|
||||||
|
if fr.flags.Changed(name) {
|
||||||
|
var value int
|
||||||
|
if value, err = fr.flags.GetInt(name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cb(fr.cfg, value)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type callback[T any] func(cgf *types.Config, value T)
|
||||||
237
internal/config/flags_test.go
Normal file
237
internal/config/flags_test.go
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
gm "go.uber.org/mock/gomock"
|
||||||
|
|
||||||
|
flagsmock "github.com/bakito/adguardhome-sync/internal/mocks/flags"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Config", func() {
|
||||||
|
var (
|
||||||
|
cfg *types.Config
|
||||||
|
flags *flagsmock.MockFlags
|
||||||
|
mockCtrl *gm.Controller
|
||||||
|
)
|
||||||
|
BeforeEach(func() {
|
||||||
|
cfg = &types.Config{
|
||||||
|
Origin: &types.AdGuardInstance{},
|
||||||
|
Replica: &types.AdGuardInstance{},
|
||||||
|
Features: types.Features{
|
||||||
|
DNS: types.DNS{
|
||||||
|
AccessLists: true,
|
||||||
|
ServerConfig: true,
|
||||||
|
Rewrites: true,
|
||||||
|
},
|
||||||
|
DHCP: types.DHCP{
|
||||||
|
ServerConfig: true,
|
||||||
|
StaticLeases: true,
|
||||||
|
},
|
||||||
|
GeneralSettings: true,
|
||||||
|
QueryLogConfig: true,
|
||||||
|
StatsConfig: true,
|
||||||
|
ClientSettings: true,
|
||||||
|
Services: true,
|
||||||
|
Filters: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mockCtrl = gm.NewController(GinkgoT())
|
||||||
|
flags = flagsmock.NewMockFlags(mockCtrl)
|
||||||
|
})
|
||||||
|
AfterEach(func() {
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
})
|
||||||
|
Context("readFlags", func() {
|
||||||
|
It("should not change the config with nil flags", func() {
|
||||||
|
clone := cfg.DeepCopy()
|
||||||
|
err := readFlags(cfg, nil)
|
||||||
|
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg).Should(Equal(clone))
|
||||||
|
})
|
||||||
|
It("should not change the config with no changed flags", func() {
|
||||||
|
clone := cfg.DeepCopy()
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
err := readFlags(cfg, flags)
|
||||||
|
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg).Should(Equal(clone))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("readFeatureFlags", func() {
|
||||||
|
It("should disable all flags", func() {
|
||||||
|
flags.EXPECT().Changed(gm.Any()).DoAndReturn(func(name string) bool {
|
||||||
|
return strings.HasPrefix(name, "feature")
|
||||||
|
}).AnyTimes()
|
||||||
|
flags.EXPECT().GetBool(gm.Any()).Return(false, nil).AnyTimes()
|
||||||
|
err := readFlags(cfg, flags)
|
||||||
|
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Features).Should(Equal(types.Features{
|
||||||
|
DNS: types.DNS{
|
||||||
|
AccessLists: false,
|
||||||
|
ServerConfig: false,
|
||||||
|
Rewrites: false,
|
||||||
|
},
|
||||||
|
DHCP: types.DHCP{
|
||||||
|
ServerConfig: false,
|
||||||
|
StaticLeases: false,
|
||||||
|
},
|
||||||
|
GeneralSettings: false,
|
||||||
|
QueryLogConfig: false,
|
||||||
|
StatsConfig: false,
|
||||||
|
ClientSettings: false,
|
||||||
|
Services: false,
|
||||||
|
Filters: false,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("readAPIFlags", func() {
|
||||||
|
It("should change all values", func() {
|
||||||
|
cfg.API = types.API{
|
||||||
|
Port: 1111,
|
||||||
|
Username: "2222",
|
||||||
|
Password: "3333",
|
||||||
|
DarkMode: false,
|
||||||
|
}
|
||||||
|
flags.EXPECT().Changed(gm.Any()).DoAndReturn(func(name string) bool {
|
||||||
|
return strings.HasPrefix(name, "api")
|
||||||
|
}).AnyTimes()
|
||||||
|
flags.EXPECT().GetInt(FlagAPIPort).Return(9999, nil)
|
||||||
|
flags.EXPECT().GetString(FlagAPIUsername).Return("aaaa", nil)
|
||||||
|
flags.EXPECT().GetString(FlagAPIPassword).Return("bbbb", nil)
|
||||||
|
flags.EXPECT().GetBool(FlagAPIDarkMode).Return(true, nil)
|
||||||
|
err := readFlags(cfg, flags)
|
||||||
|
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.API).Should(Equal(types.API{
|
||||||
|
Port: 9999,
|
||||||
|
Username: "aaaa",
|
||||||
|
Password: "bbbb",
|
||||||
|
DarkMode: true,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("readRootFlags", func() {
|
||||||
|
It("should change all values", func() {
|
||||||
|
cfg.Cron = "*/10 * * * *"
|
||||||
|
cfg.PrintConfigOnly = false
|
||||||
|
cfg.ContinueOnError = false
|
||||||
|
cfg.RunOnStart = false
|
||||||
|
|
||||||
|
flags.EXPECT().Changed(FlagCron).Return(true)
|
||||||
|
flags.EXPECT().Changed(FlagRunOnStart).Return(true)
|
||||||
|
flags.EXPECT().Changed(FlagPrintConfigOnly).Return(true)
|
||||||
|
flags.EXPECT().Changed(FlagContinueOnError).Return(true)
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
|
||||||
|
flags.EXPECT().GetString(FlagCron).Return("*/30 * * * *", nil)
|
||||||
|
flags.EXPECT().GetBool(FlagRunOnStart).Return(true, nil)
|
||||||
|
flags.EXPECT().GetBool(FlagPrintConfigOnly).Return(true, nil)
|
||||||
|
flags.EXPECT().GetBool(FlagContinueOnError).Return(true, nil)
|
||||||
|
err := readFlags(cfg, flags)
|
||||||
|
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Cron).Should(Equal("*/30 * * * *"))
|
||||||
|
Ω(cfg.RunOnStart).Should(BeTrue())
|
||||||
|
Ω(cfg.PrintConfigOnly).Should(BeTrue())
|
||||||
|
Ω(cfg.ContinueOnError).Should(BeTrue())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("readOriginFlags", func() {
|
||||||
|
It("should change all values", func() {
|
||||||
|
cfg.Origin = &types.AdGuardInstance{
|
||||||
|
URL: "1",
|
||||||
|
WebURL: "2",
|
||||||
|
APIPath: "3",
|
||||||
|
Username: "4",
|
||||||
|
Password: "5",
|
||||||
|
Cookie: "6",
|
||||||
|
InsecureSkipVerify: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
flags.EXPECT().Changed(FlagOriginURL).Return(true)
|
||||||
|
flags.EXPECT().Changed(FlagOriginWebURL).Return(true)
|
||||||
|
flags.EXPECT().Changed(FlagOriginAPIPath).Return(true)
|
||||||
|
flags.EXPECT().Changed(FlagOriginUsername).Return(true)
|
||||||
|
flags.EXPECT().Changed(FlagOriginPassword).Return(true)
|
||||||
|
flags.EXPECT().Changed(FlagOriginCookie).Return(true)
|
||||||
|
flags.EXPECT().Changed(FlagOriginISV).Return(true)
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
|
||||||
|
flags.EXPECT().GetString(FlagOriginURL).Return("a", nil)
|
||||||
|
flags.EXPECT().GetString(FlagOriginWebURL).Return("b", nil)
|
||||||
|
flags.EXPECT().GetString(FlagOriginAPIPath).Return("c", nil)
|
||||||
|
flags.EXPECT().GetString(FlagOriginUsername).Return("d", nil)
|
||||||
|
flags.EXPECT().GetString(FlagOriginPassword).Return("e", nil)
|
||||||
|
flags.EXPECT().GetString(FlagOriginCookie).Return("f", nil)
|
||||||
|
flags.EXPECT().GetBool(FlagOriginISV).Return(true, nil)
|
||||||
|
err := readFlags(cfg, flags)
|
||||||
|
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Origin).Should(Equal(&types.AdGuardInstance{
|
||||||
|
URL: "a",
|
||||||
|
WebURL: "b",
|
||||||
|
APIPath: "c",
|
||||||
|
Username: "d",
|
||||||
|
Password: "e",
|
||||||
|
Cookie: "f",
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("readReplicaFlags", func() {
|
||||||
|
It("should change all values", func() {
|
||||||
|
cfg.Replica = &types.AdGuardInstance{
|
||||||
|
URL: "1",
|
||||||
|
WebURL: "2",
|
||||||
|
APIPath: "3",
|
||||||
|
Username: "4",
|
||||||
|
Password: "5",
|
||||||
|
Cookie: "6",
|
||||||
|
InsecureSkipVerify: false,
|
||||||
|
AutoSetup: false,
|
||||||
|
InterfaceName: "7",
|
||||||
|
}
|
||||||
|
|
||||||
|
flags.EXPECT().Changed(FlagReplicaURL).Return(true)
|
||||||
|
flags.EXPECT().Changed(FlagReplicaWebURL).Return(true)
|
||||||
|
flags.EXPECT().Changed(FlagReplicaAPIPath).Return(true)
|
||||||
|
flags.EXPECT().Changed(FlagReplicaUsername).Return(true)
|
||||||
|
flags.EXPECT().Changed(FlagReplicaPassword).Return(true)
|
||||||
|
flags.EXPECT().Changed(FlagReplicaCookie).Return(true)
|
||||||
|
flags.EXPECT().Changed(FlagReplicaISV).Return(true)
|
||||||
|
flags.EXPECT().Changed(FlagReplicaAutoSetup).Return(true)
|
||||||
|
flags.EXPECT().Changed(FlagReplicaInterfaceName).Return(true)
|
||||||
|
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||||
|
|
||||||
|
flags.EXPECT().GetString(FlagReplicaURL).Return("a", nil)
|
||||||
|
flags.EXPECT().GetString(FlagReplicaWebURL).Return("b", nil)
|
||||||
|
flags.EXPECT().GetString(FlagReplicaAPIPath).Return("c", nil)
|
||||||
|
flags.EXPECT().GetString(FlagReplicaUsername).Return("d", nil)
|
||||||
|
flags.EXPECT().GetString(FlagReplicaPassword).Return("e", nil)
|
||||||
|
flags.EXPECT().GetString(FlagReplicaCookie).Return("f", nil)
|
||||||
|
flags.EXPECT().GetBool(FlagReplicaISV).Return(true, nil)
|
||||||
|
flags.EXPECT().GetBool(FlagReplicaAutoSetup).Return(true, nil)
|
||||||
|
flags.EXPECT().GetString(FlagReplicaInterfaceName).Return("g", nil)
|
||||||
|
err := readFlags(cfg, flags)
|
||||||
|
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Replica).Should(Equal(&types.AdGuardInstance{
|
||||||
|
URL: "a",
|
||||||
|
WebURL: "b",
|
||||||
|
APIPath: "c",
|
||||||
|
Username: "d",
|
||||||
|
Password: "e",
|
||||||
|
Cookie: "f",
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
AutoSetup: true,
|
||||||
|
InterfaceName: "g",
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
89
internal/config/print-config.go
Normal file
89
internal/config/print-config.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
_ "embed"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
|
"github.com/bakito/adguardhome-sync/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed print-config.md
|
||||||
|
var printConfigTemplate string
|
||||||
|
|
||||||
|
func (ac *AppConfig) Print() error {
|
||||||
|
originVersion := aghVersion(*ac.cfg.Origin)
|
||||||
|
var replicaVersions []string
|
||||||
|
for _, replica := range ac.cfg.Replicas {
|
||||||
|
replicaVersions = append(replicaVersions, aghVersion(replica))
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := ac.printInternal(os.Environ(), originVersion, replicaVersions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof(
|
||||||
|
"Printing adguardhome-sync aggregated config (THE APPLICATION WILL NOT START IN THIS MODE):\n%s",
|
||||||
|
out,
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func aghVersion(i types.AdGuardInstance) string {
|
||||||
|
cl, err := client.New(i)
|
||||||
|
if err != nil {
|
||||||
|
return "N/A"
|
||||||
|
}
|
||||||
|
stats, err := cl.Status()
|
||||||
|
if err != nil {
|
||||||
|
return "N/A"
|
||||||
|
}
|
||||||
|
return stats.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *AppConfig) printInternal(env []string, originVersion string, replicaVersions []string) (string, error) {
|
||||||
|
config, err := yaml.Marshal(ac.Get())
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
funcMap := template.FuncMap{
|
||||||
|
// The name "inc" is what the function will be called in the template text.
|
||||||
|
"inc": func(i int) int {
|
||||||
|
return i + 1
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := template.New("printConfigTemplate").Funcs(funcMap).Parse(printConfigTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(env)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
err = t.Execute(&buf, map[string]any{
|
||||||
|
"Version": version.Version,
|
||||||
|
"Build": version.Build,
|
||||||
|
"OperatingSystem": runtime.GOOS,
|
||||||
|
"Architecture": runtime.GOARCH,
|
||||||
|
"AggregatedConfig": string(config),
|
||||||
|
"ConfigFilePath": ac.filePath,
|
||||||
|
"ConfigFileContent": ac.content,
|
||||||
|
"EnvironmentVariables": strings.Join(env, "\n"),
|
||||||
|
"OriginVersion": originVersion,
|
||||||
|
"ReplicaVersions": replicaVersions,
|
||||||
|
})
|
||||||
|
return buf.String(), err
|
||||||
|
}
|
||||||
36
internal/config/print-config.md
Normal file
36
internal/config/print-config.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<!-- PLEASE COPY THE FOLLOWING OUTPUT AS IS INTO THE GITHUB ISSUE (Don't forget to mask your usernames, passwords, IPs and other sensitive information when using this in an issue ) -->
|
||||||
|
|
||||||
|
### Runtime
|
||||||
|
|
||||||
|
AdguardHome-Sync Version: {{ .Version }}
|
||||||
|
Build: {{ .Build }}
|
||||||
|
OperatingSystem: {{ .OperatingSystem }}
|
||||||
|
Architecture: {{ .Architecture }}
|
||||||
|
OriginVersion: {{ .OriginVersion }}
|
||||||
|
ReplicaVersions:
|
||||||
|
{{- range $i,$rep := .ReplicaVersions }}
|
||||||
|
- Replica {{ inc $i }}: {{ $rep }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
### AdGuardHome sync aggregated config
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
{{ .AggregatedConfig }}
|
||||||
|
```
|
||||||
|
{{- if .ConfigFilePath }}
|
||||||
|
### AdGuardHome sync unmodified config file
|
||||||
|
|
||||||
|
Config file path: {{ .ConfigFilePath }}
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
{{ .ConfigFileContent }}
|
||||||
|
```
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
```ini
|
||||||
|
{{ .EnvironmentVariables }}
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- END OF GITHUB ISSUE CONTENT -->
|
||||||
60
internal/config/print-config_test.go
Normal file
60
internal/config/print-config_test.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/test/matchers"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
|
"github.com/bakito/adguardhome-sync/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("AppConfig", func() {
|
||||||
|
var (
|
||||||
|
ac *AppConfig
|
||||||
|
env []string
|
||||||
|
)
|
||||||
|
BeforeEach(func() {
|
||||||
|
ac = &AppConfig{
|
||||||
|
cfg: &types.Config{
|
||||||
|
Origin: &types.AdGuardInstance{
|
||||||
|
URL: "https://ha.xxxx.net:3000",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
content: `
|
||||||
|
origin:
|
||||||
|
url: https://ha.xxxx.net:3000
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
env = []string{"FOO=foo", "BAR=bar"}
|
||||||
|
})
|
||||||
|
Context("printInternal", func() {
|
||||||
|
It("should printInternal config without file", func() {
|
||||||
|
out, err := ac.printInternal(env, "v0.0.1", []string{"v0.0.2"})
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(out).
|
||||||
|
Should(matchers.EqualIgnoringLineEndings(fmt.Sprintf(expected(1), version.Version, version.Build, runtime.GOOS, runtime.GOARCH)))
|
||||||
|
})
|
||||||
|
It("should printInternal config with file", func() {
|
||||||
|
ac.filePath = "config.yaml"
|
||||||
|
out, err := ac.printInternal(env, "v0.0.1", []string{"v0.0.2"})
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(out).
|
||||||
|
Should(matchers.EqualIgnoringLineEndings(fmt.Sprintf(expected(2), version.Version, version.Build, runtime.GOOS, runtime.GOARCH)))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
func expected(id int) string {
|
||||||
|
b, err := os.ReadFile(
|
||||||
|
filepath.Join("..", "..", "testdata", "config", fmt.Sprintf("print-config_test_expected%d.md", id)),
|
||||||
|
)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
59
internal/config/validate.go
Normal file
59
internal/config/validate.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/santhosh-tekuri/jsonschema/v6"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
const schemaURL = "config-schema.json"
|
||||||
|
|
||||||
|
//go:embed config-schema.json
|
||||||
|
var schemaData string
|
||||||
|
|
||||||
|
func validateSchema(cfgFile string) error {
|
||||||
|
// ignore if file not exists
|
||||||
|
if _, err := os.Stat(cfgFile); err != nil {
|
||||||
|
// Config file does not exist or is not readable - ignore it
|
||||||
|
//nolint:nilerr
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Load YAML file
|
||||||
|
yamlContent, err := os.ReadFile(cfgFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("config file %q is invalid: %w", cfgFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return validateYAML(yamlContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateYAML(yamlContent []byte) error {
|
||||||
|
if yamlContent == nil || strings.TrimSpace(string(yamlContent)) == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert YAML to JSON
|
||||||
|
var yamlData any
|
||||||
|
err := yaml.Unmarshal(yamlContent, &yamlData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load JSON schema
|
||||||
|
sch, err := jsonschema.UnmarshalJSON(strings.NewReader(schemaData))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c := jsonschema.NewCompiler()
|
||||||
|
if err := c.AddResource(schemaURL, sch); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
schema := c.MustCompile(schemaURL)
|
||||||
|
// validateSchema
|
||||||
|
return schema.Validate(yamlData)
|
||||||
|
}
|
||||||
45
internal/config/validate_test.go
Normal file
45
internal/config/validate_test.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-faker/faker/v4"
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Config", func() {
|
||||||
|
Context("validateSchema", func() {
|
||||||
|
DescribeTable("validateSchema config",
|
||||||
|
func(configFile string, expectFail bool) {
|
||||||
|
err := validateSchema(configFile)
|
||||||
|
if expectFail {
|
||||||
|
Ω(err).Should(HaveOccurred())
|
||||||
|
} else {
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Entry(`Should be valid`, "../../testdata/config/config-valid.yaml", false),
|
||||||
|
Entry(`Should be valid if file doesn't exist`, "../../testdata/config/foo.bar", false),
|
||||||
|
Entry(`Should fail if file is not yaml`, "../../go.mod", true),
|
||||||
|
)
|
||||||
|
It("validate config with all fields randomly populated", func() {
|
||||||
|
cfg := &types.Config{}
|
||||||
|
|
||||||
|
err := faker.FakeData(cfg)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
|
||||||
|
data, err := yaml.Marshal(&cfg)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = validateYAML(data)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("validate config with empty file", func() {
|
||||||
|
var data []byte
|
||||||
|
err := validateYAML(data)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
logHistorySize = 50
|
logHistorySize = 50
|
||||||
envLogLevel = "LOG_LEVEL"
|
envLogLevel = "LOG_LEVEL"
|
||||||
|
envLogFormat = "LOG_FORMAT"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -17,7 +18,7 @@ var (
|
|||||||
logs []string
|
logs []string
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetLogger returns a named logger
|
// GetLogger returns a named logger.
|
||||||
func GetLogger(name string) *zap.SugaredLogger {
|
func GetLogger(name string) *zap.SugaredLogger {
|
||||||
return rootLogger.Named(name).Sugar()
|
return rootLogger.Named(name).Sugar()
|
||||||
}
|
}
|
||||||
@@ -31,14 +32,23 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
format := "console"
|
||||||
|
if fmt, ok := os.LookupEnv(envLogFormat); ok {
|
||||||
|
format = fmt
|
||||||
|
}
|
||||||
|
|
||||||
cfg := zap.Config{
|
cfg := zap.Config{
|
||||||
Level: zap.NewAtomicLevelAt(level),
|
Level: zap.NewAtomicLevelAt(level),
|
||||||
Development: false,
|
Development: false,
|
||||||
Encoding: "console",
|
Encoding: format,
|
||||||
EncoderConfig: zap.NewDevelopmentEncoderConfig(),
|
EncoderConfig: zap.NewProductionEncoderConfig(),
|
||||||
OutputPaths: []string{"stdout"},
|
OutputPaths: []string{"stdout"},
|
||||||
ErrorOutputPaths: []string{"stderr"},
|
ErrorOutputPaths: []string{"stderr"},
|
||||||
}
|
}
|
||||||
|
cfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||||
|
cfg.EncoderConfig.EncodeDuration = zapcore.StringDurationEncoder
|
||||||
|
cfg.EncoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
|
||||||
|
|
||||||
opt := zap.WrapCore(func(c zapcore.Core) zapcore.Core {
|
opt := zap.WrapCore(func(c zapcore.Core) zapcore.Core {
|
||||||
return zapcore.NewTee(c, &logList{
|
return zapcore.NewTee(c, &logList{
|
||||||
enc: zapcore.NewConsoleEncoder(cfg.EncoderConfig),
|
enc: zapcore.NewConsoleEncoder(cfg.EncoderConfig),
|
||||||
@@ -91,11 +101,16 @@ func (l *logList) Sync() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logs get the current logs
|
// Logs get the current logs.
|
||||||
func Logs() []string {
|
func Logs() []string {
|
||||||
return logs
|
return logs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear the current logs.
|
||||||
|
func Clear() {
|
||||||
|
logs = nil
|
||||||
|
}
|
||||||
|
|
||||||
func addFields(enc zapcore.ObjectEncoder, fields []zapcore.Field) {
|
func addFields(enc zapcore.ObjectEncoder, fields []zapcore.Field) {
|
||||||
for i := range fields {
|
for i := range fields {
|
||||||
fields[i].AddTo(enc)
|
fields[i].AddTo(enc)
|
||||||
14
internal/metrics/handler.go
Normal file
14
internal/metrics/handler.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Handler() gin.HandlerFunc {
|
||||||
|
h := promhttp.Handler()
|
||||||
|
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
h.ServeHTTP(c.Writer, c.Request)
|
||||||
|
}
|
||||||
|
}
|
||||||
297
internal/metrics/metrics.go
Normal file
297
internal/metrics/metrics.go
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const StatsTotal = "total"
|
||||||
|
|
||||||
|
var (
|
||||||
|
l = log.GetLogger("metrics")
|
||||||
|
|
||||||
|
// avgProcessingTime - Average processing time for a DNS query.
|
||||||
|
avgProcessingTime = prometheus.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "avg_processing_time",
|
||||||
|
Namespace: "adguard",
|
||||||
|
Help: "This represent the average processing time for a DNS query in s",
|
||||||
|
},
|
||||||
|
[]string{"hostname"},
|
||||||
|
)
|
||||||
|
|
||||||
|
// dnsQueries - Number of DNS queries.
|
||||||
|
dnsQueries = prometheus.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "num_dns_queries",
|
||||||
|
Namespace: "adguard",
|
||||||
|
Help: "Number of DNS queries",
|
||||||
|
},
|
||||||
|
[]string{"hostname"},
|
||||||
|
)
|
||||||
|
|
||||||
|
// blockedFiltering - Number of DNS queries blocked.
|
||||||
|
blockedFiltering = prometheus.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "num_blocked_filtering",
|
||||||
|
Namespace: "adguard",
|
||||||
|
Help: "This represent the number of domains blocked",
|
||||||
|
},
|
||||||
|
[]string{"hostname"},
|
||||||
|
)
|
||||||
|
|
||||||
|
// parentalFiltering - Number of DNS queries replaced by parental control.
|
||||||
|
parentalFiltering = prometheus.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "num_replaced_parental",
|
||||||
|
Namespace: "adguard",
|
||||||
|
Help: "This represent the number of domains blocked (parental)",
|
||||||
|
},
|
||||||
|
[]string{"hostname"},
|
||||||
|
)
|
||||||
|
|
||||||
|
// safeBrowsingFiltering - Number of DNS queries replaced by safe browsing.
|
||||||
|
safeBrowsingFiltering = prometheus.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "num_replaced_safebrowsing",
|
||||||
|
Namespace: "adguard",
|
||||||
|
Help: "This represent the number of domains blocked (safe browsing)",
|
||||||
|
},
|
||||||
|
[]string{"hostname"},
|
||||||
|
)
|
||||||
|
|
||||||
|
// safeSearchFiltering - Number of DNS queries replaced by safe search.
|
||||||
|
safeSearchFiltering = prometheus.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "num_replaced_safesearch",
|
||||||
|
Namespace: "adguard",
|
||||||
|
Help: "This represent the number of domains blocked (safe search)",
|
||||||
|
},
|
||||||
|
[]string{"hostname"},
|
||||||
|
)
|
||||||
|
|
||||||
|
// topQueries - The number of top queries.
|
||||||
|
topQueries = prometheus.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "top_queried_domains",
|
||||||
|
Namespace: "adguard",
|
||||||
|
Help: "This represent the top queried domains",
|
||||||
|
},
|
||||||
|
[]string{"hostname", "domain"},
|
||||||
|
)
|
||||||
|
|
||||||
|
// topBlocked - The number of top domains blocked.
|
||||||
|
topBlocked = prometheus.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "top_blocked_domains",
|
||||||
|
Namespace: "adguard",
|
||||||
|
Help: "This represent the top bloacked domains",
|
||||||
|
},
|
||||||
|
[]string{"hostname", "domain"},
|
||||||
|
)
|
||||||
|
|
||||||
|
// topClients - The number of top clients.
|
||||||
|
topClients = prometheus.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "top_clients",
|
||||||
|
Namespace: "adguard",
|
||||||
|
Help: "This represent the top clients",
|
||||||
|
},
|
||||||
|
[]string{"hostname", "client"},
|
||||||
|
)
|
||||||
|
|
||||||
|
// queryTypes - The type of DNS Queries (A, AAAA...)
|
||||||
|
queryTypes = prometheus.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "query_types",
|
||||||
|
Namespace: "adguard",
|
||||||
|
Help: "This represent the DNS query types",
|
||||||
|
},
|
||||||
|
[]string{"hostname", "type"},
|
||||||
|
)
|
||||||
|
|
||||||
|
// running - If Adguard is running.
|
||||||
|
running = prometheus.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "running",
|
||||||
|
Namespace: "adguard",
|
||||||
|
Help: "This represent if Adguard is running",
|
||||||
|
},
|
||||||
|
[]string{"hostname"},
|
||||||
|
)
|
||||||
|
|
||||||
|
// protectionEnabled - If Adguard protection is enabled.
|
||||||
|
protectionEnabled = prometheus.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "protection_enabled",
|
||||||
|
Namespace: "adguard",
|
||||||
|
Help: "This represent if Adguard Protection is enabled",
|
||||||
|
},
|
||||||
|
[]string{"hostname"},
|
||||||
|
)
|
||||||
|
// aghsSyncDuration - the sync curation in seconds.
|
||||||
|
aghsSyncDuration = prometheus.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "sync_duration_seconds",
|
||||||
|
Namespace: "adguard_home_sync",
|
||||||
|
Help: "This represents the duration of the last sync in seconds",
|
||||||
|
},
|
||||||
|
[]string{"hostname"},
|
||||||
|
)
|
||||||
|
// aghsSyncSuccessful - the sync result.
|
||||||
|
aghsSyncSuccessful = prometheus.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "sync_successful",
|
||||||
|
Namespace: "adguard_home_sync",
|
||||||
|
Help: "This represents the whether the last sync was successful",
|
||||||
|
},
|
||||||
|
[]string{"hostname"},
|
||||||
|
)
|
||||||
|
stats = OverallStats{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Init initializes all Prometheus metrics made available by AdGuard exporter.
|
||||||
|
func Init() {
|
||||||
|
initMetric("avg_processing_time", avgProcessingTime)
|
||||||
|
initMetric("num_dns_queries", dnsQueries)
|
||||||
|
initMetric("num_blocked_filtering", blockedFiltering)
|
||||||
|
initMetric("num_replaced_parental", parentalFiltering)
|
||||||
|
initMetric("num_replaced_safebrowsing", safeBrowsingFiltering)
|
||||||
|
initMetric("num_replaced_safesearch", safeSearchFiltering)
|
||||||
|
initMetric("top_queried_domains", topQueries)
|
||||||
|
initMetric("top_blocked_domains", topBlocked)
|
||||||
|
initMetric("top_clients", topClients)
|
||||||
|
initMetric("query_types", queryTypes)
|
||||||
|
initMetric("running", running)
|
||||||
|
initMetric("protection_enabled", protectionEnabled)
|
||||||
|
initMetric("sync_duration_seconds", aghsSyncDuration)
|
||||||
|
initMetric("sync_successful", aghsSyncSuccessful)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initMetric(name string, metric *prometheus.GaugeVec) {
|
||||||
|
prometheus.MustRegister(metric)
|
||||||
|
l.With("name", name).Info("New Prometheus metric registered")
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateInstances(iml InstanceMetricsList) {
|
||||||
|
for _, im := range iml.Metrics {
|
||||||
|
updateMetrics(im)
|
||||||
|
stats[im.HostName] = im.Stats
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Debug("updated")
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateResult(host string, ok bool, duration float64) {
|
||||||
|
if ok {
|
||||||
|
aghsSyncSuccessful.WithLabelValues(host).Set(1)
|
||||||
|
} else {
|
||||||
|
aghsSyncSuccessful.WithLabelValues(host).Set(0)
|
||||||
|
}
|
||||||
|
aghsSyncDuration.WithLabelValues(host).Set(duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateMetrics(im InstanceMetrics) {
|
||||||
|
// Status
|
||||||
|
isRunning := 0
|
||||||
|
if im.Status.Running {
|
||||||
|
isRunning = 1
|
||||||
|
}
|
||||||
|
running.WithLabelValues(im.HostName).Set(float64(isRunning))
|
||||||
|
|
||||||
|
isProtected := 0
|
||||||
|
if im.Status.ProtectionEnabled {
|
||||||
|
isProtected = 1
|
||||||
|
}
|
||||||
|
protectionEnabled.WithLabelValues(im.HostName).Set(float64(isProtected))
|
||||||
|
|
||||||
|
// Stats
|
||||||
|
avgProcessingTime.WithLabelValues(im.HostName).Set(safeMetric(im.Stats.AvgProcessingTime))
|
||||||
|
dnsQueries.WithLabelValues(im.HostName).Set(safeMetric(im.Stats.NumDnsQueries))
|
||||||
|
blockedFiltering.WithLabelValues(im.HostName).Set(safeMetric(im.Stats.NumBlockedFiltering))
|
||||||
|
parentalFiltering.WithLabelValues(im.HostName).Set(safeMetric(im.Stats.NumReplacedParental))
|
||||||
|
safeBrowsingFiltering.WithLabelValues(im.HostName).Set(safeMetric(im.Stats.NumReplacedSafebrowsing))
|
||||||
|
safeSearchFiltering.WithLabelValues(im.HostName).Set(safeMetric(im.Stats.NumReplacedSafesearch))
|
||||||
|
|
||||||
|
if im.Stats.TopQueriedDomains != nil {
|
||||||
|
for _, tq := range *im.Stats.TopQueriedDomains {
|
||||||
|
for domain, value := range tq.AdditionalProperties {
|
||||||
|
topQueries.WithLabelValues(im.HostName, domain).Set(float64(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if im.Stats.TopBlockedDomains != nil {
|
||||||
|
for _, tb := range *im.Stats.TopBlockedDomains {
|
||||||
|
for domain, value := range tb.AdditionalProperties {
|
||||||
|
topBlocked.WithLabelValues(im.HostName, domain).Set(float64(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if im.Stats.TopClients != nil {
|
||||||
|
for _, tc := range *im.Stats.TopClients {
|
||||||
|
for source, value := range tc.AdditionalProperties {
|
||||||
|
topClients.WithLabelValues(im.HostName, source).Set(float64(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogQuery
|
||||||
|
m := make(map[string]int)
|
||||||
|
if im.QueryLog != nil && im.QueryLog.Data != nil {
|
||||||
|
logdata := *im.QueryLog.Data
|
||||||
|
for _, ld := range logdata {
|
||||||
|
if ld.Answer != nil {
|
||||||
|
dnsanswer := *ld.Answer
|
||||||
|
if len(dnsanswer) > 0 {
|
||||||
|
for _, dnsa := range dnsanswer {
|
||||||
|
dnsType := *dnsa.Type
|
||||||
|
m[dnsType]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range m {
|
||||||
|
queryTypes.WithLabelValues(im.HostName, key).Set(float64(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type InstanceMetricsList struct {
|
||||||
|
Metrics []InstanceMetrics `faker:"slice_len=5"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InstanceMetrics struct {
|
||||||
|
HostName string
|
||||||
|
Status *model.ServerStatus
|
||||||
|
Stats *model.Stats
|
||||||
|
QueryLog *model.QueryLog
|
||||||
|
}
|
||||||
|
|
||||||
|
type OverallStats map[string]*model.Stats
|
||||||
|
|
||||||
|
func (os OverallStats) consolidate() OverallStats {
|
||||||
|
consolidated := OverallStats{StatsTotal: model.NewStats()}
|
||||||
|
for host, stats := range os {
|
||||||
|
consolidated[host] = stats
|
||||||
|
consolidated[StatsTotal].Add(stats)
|
||||||
|
}
|
||||||
|
return consolidated
|
||||||
|
}
|
||||||
|
|
||||||
|
func safeMetric[T int | float64 | float32](v *T) float64 {
|
||||||
|
if v == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return float64(*v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStats() OverallStats {
|
||||||
|
return stats.consolidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (os OverallStats) Total() *model.Stats {
|
||||||
|
return os[StatsTotal]
|
||||||
|
}
|
||||||
13
internal/metrics/metrics_suite_test.go
Normal file
13
internal/metrics/metrics_suite_test.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package metrics_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSync(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Metrics Suite")
|
||||||
|
}
|
||||||
102
internal/metrics/metrics_test.go
Normal file
102
internal/metrics/metrics_test.go
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-faker/faker/v4"
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"k8s.io/utils/ptr"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Metrics", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
stats = make(OverallStats)
|
||||||
|
})
|
||||||
|
Context("UpdateInstances / getStats", func() {
|
||||||
|
It("generate correct stats", func() {
|
||||||
|
UpdateInstances(InstanceMetricsList{[]InstanceMetrics{
|
||||||
|
{HostName: "foo", Status: &model.ServerStatus{}, Stats: &model.Stats{
|
||||||
|
NumDnsQueries: ptr.To(100),
|
||||||
|
DnsQueries: ptr.To(
|
||||||
|
[]int{10, 20, 30, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||||
|
),
|
||||||
|
}},
|
||||||
|
{HostName: "bar", Status: &model.ServerStatus{}, Stats: &model.Stats{
|
||||||
|
NumDnsQueries: ptr.To(200),
|
||||||
|
DnsQueries: ptr.To(
|
||||||
|
[]int{20, 40, 60, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||||
|
),
|
||||||
|
}},
|
||||||
|
{HostName: "aaa", Status: &model.ServerStatus{}, Stats: &model.Stats{
|
||||||
|
NumDnsQueries: ptr.To(300),
|
||||||
|
DnsQueries: ptr.To(
|
||||||
|
[]int{30, 60, 90, 120, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||||
|
),
|
||||||
|
}},
|
||||||
|
}})
|
||||||
|
Ω(stats).Should(HaveKey("foo"))
|
||||||
|
Ω(stats["foo"].NumDnsQueries).Should(Equal(ptr.To(100)))
|
||||||
|
Ω(stats).Should(HaveKey("bar"))
|
||||||
|
Ω(stats["bar"].NumDnsQueries).Should(Equal(ptr.To(200)))
|
||||||
|
Ω(stats).Should(HaveKey("aaa"))
|
||||||
|
Ω(stats["aaa"].NumDnsQueries).Should(Equal(ptr.To(300)))
|
||||||
|
|
||||||
|
os := getStats()
|
||||||
|
tot := os.Total()
|
||||||
|
Ω(*tot.NumDnsQueries).Should(Equal(600))
|
||||||
|
Ω(
|
||||||
|
*tot.DnsQueries,
|
||||||
|
).Should(Equal([]int{60, 120, 180, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))
|
||||||
|
|
||||||
|
foo := os["foo"]
|
||||||
|
bar := os["bar"]
|
||||||
|
aaa := os["aaa"]
|
||||||
|
|
||||||
|
Ω(*foo.NumDnsQueries).Should(Equal(100))
|
||||||
|
Ω(*bar.NumDnsQueries).Should(Equal(200))
|
||||||
|
Ω(*aaa.NumDnsQueries).Should(Equal(300))
|
||||||
|
Ω(
|
||||||
|
*foo.DnsQueries,
|
||||||
|
).Should(Equal([]int{10, 20, 30, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))
|
||||||
|
Ω(
|
||||||
|
*bar.DnsQueries,
|
||||||
|
).Should(Equal([]int{20, 40, 60, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))
|
||||||
|
Ω(
|
||||||
|
*aaa.DnsQueries,
|
||||||
|
).Should(Equal([]int{30, 60, 90, 120, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("StatsGraph", func() {
|
||||||
|
var metrics InstanceMetricsList
|
||||||
|
BeforeEach(func() {
|
||||||
|
err := faker.FakeData(&metrics)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should provide correct results with faked values", func() {
|
||||||
|
UpdateInstances(metrics)
|
||||||
|
|
||||||
|
_, dns, blocked, malware, adult := StatsGraph()
|
||||||
|
|
||||||
|
verifyStats(dns)
|
||||||
|
verifyStats(blocked)
|
||||||
|
verifyStats(malware)
|
||||||
|
verifyStats(adult)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
func verifyStats(lines []Line) {
|
||||||
|
var total Line
|
||||||
|
sum := make([]int, len(lines[0].Data))
|
||||||
|
for _, l := range lines {
|
||||||
|
if l.Title == labelTotal {
|
||||||
|
total = l
|
||||||
|
} else {
|
||||||
|
for i, d := range l.Data {
|
||||||
|
sum[i] += d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ω(sum).Should(Equal(total.Data))
|
||||||
|
}
|
||||||
130
internal/metrics/stats.go
Normal file
130
internal/metrics/stats.go
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
const labelTotal = "Total"
|
||||||
|
|
||||||
|
var (
|
||||||
|
blue = []int{78, 141, 245}
|
||||||
|
blueAlternatives = [][]int{
|
||||||
|
{44, 95, 163},
|
||||||
|
{122, 166, 247},
|
||||||
|
{30, 61, 92},
|
||||||
|
{93, 158, 255},
|
||||||
|
{58, 123, 213},
|
||||||
|
}
|
||||||
|
|
||||||
|
red = []int{255, 94, 94}
|
||||||
|
redAlternatives = [][]int{
|
||||||
|
{204, 59, 59},
|
||||||
|
{255, 127, 127},
|
||||||
|
{140, 36, 36},
|
||||||
|
{255, 153, 153},
|
||||||
|
{255, 66, 66},
|
||||||
|
}
|
||||||
|
|
||||||
|
yellow = []int{232, 198, 78}
|
||||||
|
yellowAlternatives = [][]int{
|
||||||
|
{196, 163, 60},
|
||||||
|
{255, 220, 110},
|
||||||
|
{140, 114, 36},
|
||||||
|
{250, 233, 156},
|
||||||
|
{212, 180, 84},
|
||||||
|
}
|
||||||
|
|
||||||
|
green = []int{110, 224, 122}
|
||||||
|
greenAlternatives = [][]int{
|
||||||
|
{68, 160, 80},
|
||||||
|
{142, 255, 158},
|
||||||
|
{44, 140, 63},
|
||||||
|
{163, 255, 192},
|
||||||
|
{85, 198, 102},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func StatsGraph() (t *model.Stats, dns, blocked, malware, adult []Line) {
|
||||||
|
s := getStats()
|
||||||
|
t = s.Total()
|
||||||
|
dns = graphLines(t, s, blue, blueAlternatives, func(s *model.Stats) []int {
|
||||||
|
return safeStats(s.DnsQueries)
|
||||||
|
})
|
||||||
|
blocked = graphLines(t, s, red, redAlternatives, func(s *model.Stats) []int {
|
||||||
|
return safeStats(s.BlockedFiltering)
|
||||||
|
})
|
||||||
|
malware = graphLines(t, s, green, greenAlternatives, func(s *model.Stats) []int {
|
||||||
|
return safeStats(s.ReplacedSafebrowsing)
|
||||||
|
})
|
||||||
|
adult = graphLines(t, s, yellow, yellowAlternatives, func(s *model.Stats) []int {
|
||||||
|
return safeStats(s.ReplacedParental)
|
||||||
|
})
|
||||||
|
|
||||||
|
return t, dns, blocked, malware, adult
|
||||||
|
}
|
||||||
|
|
||||||
|
func safeStats(stats *[]int) []int {
|
||||||
|
if stats == nil {
|
||||||
|
return make([]int, 0)
|
||||||
|
}
|
||||||
|
return *stats
|
||||||
|
}
|
||||||
|
|
||||||
|
func graphLines(
|
||||||
|
t *model.Stats,
|
||||||
|
s OverallStats,
|
||||||
|
baseColor []int,
|
||||||
|
altColors [][]int,
|
||||||
|
dataCB func(s *model.Stats) []int,
|
||||||
|
) []Line {
|
||||||
|
g := &graph{
|
||||||
|
total: Line{
|
||||||
|
Fill: true,
|
||||||
|
Title: labelTotal,
|
||||||
|
Data: dataCB(t),
|
||||||
|
R: baseColor[0],
|
||||||
|
G: baseColor[1],
|
||||||
|
B: baseColor[2],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var i int
|
||||||
|
for name, data := range s {
|
||||||
|
if name != StatsTotal {
|
||||||
|
g.replicas = append(g.replicas, Line{
|
||||||
|
Fill: false,
|
||||||
|
Title: name,
|
||||||
|
Data: dataCB(data),
|
||||||
|
R: altColors[i%len(altColors)][0],
|
||||||
|
G: altColors[i%len(altColors)][1],
|
||||||
|
B: altColors[i%len(altColors)][2],
|
||||||
|
})
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := []Line{g.total}
|
||||||
|
|
||||||
|
slices.SortFunc(g.replicas, func(a, b Line) int {
|
||||||
|
return strings.Compare(a.Title, b.Title)
|
||||||
|
})
|
||||||
|
lines = append(lines, g.replicas...)
|
||||||
|
return lines
|
||||||
|
}
|
||||||
|
|
||||||
|
type graph struct {
|
||||||
|
total Line
|
||||||
|
replicas []Line
|
||||||
|
}
|
||||||
|
|
||||||
|
type Line struct {
|
||||||
|
Data []int `json:"data"`
|
||||||
|
R int `json:"r"`
|
||||||
|
G int `json:"g"`
|
||||||
|
B int `json:"b"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Fill bool `json:"fill"`
|
||||||
|
}
|
||||||
682
internal/mocks/client/mock.go
Normal file
682
internal/mocks/client/mock.go
Normal file
@@ -0,0 +1,682 @@
|
|||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: github.com/bakito/adguardhome-sync/internal/client (interfaces: Client)
|
||||||
|
//
|
||||||
|
// Generated by this command:
|
||||||
|
//
|
||||||
|
// mockgen -package client -destination internal/mocks/client/mock.go github.com/bakito/adguardhome-sync/internal/client Client
|
||||||
|
//
|
||||||
|
|
||||||
|
// Package client is a generated GoMock package.
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
model "github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
|
gomock "go.uber.org/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockClient is a mock of Client interface.
|
||||||
|
type MockClient struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockClientMockRecorder
|
||||||
|
isgomock struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockClientMockRecorder is the mock recorder for MockClient.
|
||||||
|
type MockClientMockRecorder struct {
|
||||||
|
mock *MockClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockClient creates a new mock instance.
|
||||||
|
func NewMockClient(ctrl *gomock.Controller) *MockClient {
|
||||||
|
mock := &MockClient{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockClientMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockClient) EXPECT() *MockClientMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessList mocks base method.
|
||||||
|
func (m *MockClient) AccessList() (*model.AccessList, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "AccessList")
|
||||||
|
ret0, _ := ret[0].(*model.AccessList)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessList indicates an expected call of AccessList.
|
||||||
|
func (mr *MockClientMockRecorder) AccessList() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AccessList", reflect.TypeOf((*MockClient)(nil).AccessList))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddClient mocks base method.
|
||||||
|
func (m *MockClient) AddClient(client *model.Client) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "AddClient", client)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddClient indicates an expected call of AddClient.
|
||||||
|
func (mr *MockClientMockRecorder) AddClient(client any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddClient", reflect.TypeOf((*MockClient)(nil).AddClient), client)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddDHCPStaticLease mocks base method.
|
||||||
|
func (m *MockClient) AddDHCPStaticLease(lease model.DhcpStaticLease) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "AddDHCPStaticLease", lease)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddDHCPStaticLease indicates an expected call of AddDHCPStaticLease.
|
||||||
|
func (mr *MockClientMockRecorder) AddDHCPStaticLease(lease any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddDHCPStaticLease", reflect.TypeOf((*MockClient)(nil).AddDHCPStaticLease), lease)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFilter mocks base method.
|
||||||
|
func (m *MockClient) AddFilter(whitelist bool, f model.Filter) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "AddFilter", whitelist, f)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFilter indicates an expected call of AddFilter.
|
||||||
|
func (mr *MockClientMockRecorder) AddFilter(whitelist, f any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddFilter", reflect.TypeOf((*MockClient)(nil).AddFilter), whitelist, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRewriteEntries mocks base method.
|
||||||
|
func (m *MockClient) AddRewriteEntries(e ...model.RewriteEntry) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
varargs := []any{}
|
||||||
|
for _, a := range e {
|
||||||
|
varargs = append(varargs, a)
|
||||||
|
}
|
||||||
|
ret := m.ctrl.Call(m, "AddRewriteEntries", varargs...)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRewriteEntries indicates an expected call of AddRewriteEntries.
|
||||||
|
func (mr *MockClientMockRecorder) AddRewriteEntries(e ...any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRewriteEntries", reflect.TypeOf((*MockClient)(nil).AddRewriteEntries), e...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockedServicesSchedule mocks base method.
|
||||||
|
func (m *MockClient) BlockedServicesSchedule() (*model.BlockedServicesSchedule, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "BlockedServicesSchedule")
|
||||||
|
ret0, _ := ret[0].(*model.BlockedServicesSchedule)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockedServicesSchedule indicates an expected call of BlockedServicesSchedule.
|
||||||
|
func (mr *MockClientMockRecorder) BlockedServicesSchedule() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockedServicesSchedule", reflect.TypeOf((*MockClient)(nil).BlockedServicesSchedule))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clients mocks base method.
|
||||||
|
func (m *MockClient) Clients() (*model.Clients, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Clients")
|
||||||
|
ret0, _ := ret[0].(*model.Clients)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clients indicates an expected call of Clients.
|
||||||
|
func (mr *MockClientMockRecorder) Clients() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clients", reflect.TypeOf((*MockClient)(nil).Clients))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNSConfig mocks base method.
|
||||||
|
func (m *MockClient) DNSConfig() (*model.DNSConfig, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "DNSConfig")
|
||||||
|
ret0, _ := ret[0].(*model.DNSConfig)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNSConfig indicates an expected call of DNSConfig.
|
||||||
|
func (mr *MockClientMockRecorder) DNSConfig() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DNSConfig", reflect.TypeOf((*MockClient)(nil).DNSConfig))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteClient mocks base method.
|
||||||
|
func (m *MockClient) DeleteClient(client *model.Client) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "DeleteClient", client)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteClient indicates an expected call of DeleteClient.
|
||||||
|
func (mr *MockClientMockRecorder) DeleteClient(client any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteClient", reflect.TypeOf((*MockClient)(nil).DeleteClient), client)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDHCPStaticLease mocks base method.
|
||||||
|
func (m *MockClient) DeleteDHCPStaticLease(lease model.DhcpStaticLease) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "DeleteDHCPStaticLease", lease)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDHCPStaticLease indicates an expected call of DeleteDHCPStaticLease.
|
||||||
|
func (mr *MockClientMockRecorder) DeleteDHCPStaticLease(lease any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDHCPStaticLease", reflect.TypeOf((*MockClient)(nil).DeleteDHCPStaticLease), lease)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteFilter mocks base method.
|
||||||
|
func (m *MockClient) DeleteFilter(whitelist bool, f model.Filter) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "DeleteFilter", whitelist, f)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteFilter indicates an expected call of DeleteFilter.
|
||||||
|
func (mr *MockClientMockRecorder) DeleteFilter(whitelist, f any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteFilter", reflect.TypeOf((*MockClient)(nil).DeleteFilter), whitelist, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRewriteEntries mocks base method.
|
||||||
|
func (m *MockClient) DeleteRewriteEntries(e ...model.RewriteEntry) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
varargs := []any{}
|
||||||
|
for _, a := range e {
|
||||||
|
varargs = append(varargs, a)
|
||||||
|
}
|
||||||
|
ret := m.ctrl.Call(m, "DeleteRewriteEntries", varargs...)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRewriteEntries indicates an expected call of DeleteRewriteEntries.
|
||||||
|
func (mr *MockClientMockRecorder) DeleteRewriteEntries(e ...any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRewriteEntries", reflect.TypeOf((*MockClient)(nil).DeleteRewriteEntries), e...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DhcpConfig mocks base method.
|
||||||
|
func (m *MockClient) DhcpConfig() (*model.DhcpStatus, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "DhcpConfig")
|
||||||
|
ret0, _ := ret[0].(*model.DhcpStatus)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// DhcpConfig indicates an expected call of DhcpConfig.
|
||||||
|
func (mr *MockClientMockRecorder) DhcpConfig() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DhcpConfig", reflect.TypeOf((*MockClient)(nil).DhcpConfig))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filtering mocks base method.
|
||||||
|
func (m *MockClient) Filtering() (*model.FilterStatus, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Filtering")
|
||||||
|
ret0, _ := ret[0].(*model.FilterStatus)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filtering indicates an expected call of Filtering.
|
||||||
|
func (mr *MockClientMockRecorder) Filtering() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Filtering", reflect.TypeOf((*MockClient)(nil).Filtering))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Host mocks base method.
|
||||||
|
func (m *MockClient) Host() string {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Host")
|
||||||
|
ret0, _ := ret[0].(string)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Host indicates an expected call of Host.
|
||||||
|
func (mr *MockClientMockRecorder) Host() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Host", reflect.TypeOf((*MockClient)(nil).Host))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parental mocks base method.
|
||||||
|
func (m *MockClient) Parental() (bool, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Parental")
|
||||||
|
ret0, _ := ret[0].(bool)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parental indicates an expected call of Parental.
|
||||||
|
func (mr *MockClientMockRecorder) Parental() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Parental", reflect.TypeOf((*MockClient)(nil).Parental))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProfileInfo mocks base method.
|
||||||
|
func (m *MockClient) ProfileInfo() (*model.ProfileInfo, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "ProfileInfo")
|
||||||
|
ret0, _ := ret[0].(*model.ProfileInfo)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProfileInfo indicates an expected call of ProfileInfo.
|
||||||
|
func (mr *MockClientMockRecorder) ProfileInfo() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProfileInfo", reflect.TypeOf((*MockClient)(nil).ProfileInfo))
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryLog mocks base method.
|
||||||
|
func (m *MockClient) QueryLog(limit int) (*model.QueryLog, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "QueryLog", limit)
|
||||||
|
ret0, _ := ret[0].(*model.QueryLog)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryLog indicates an expected call of QueryLog.
|
||||||
|
func (mr *MockClientMockRecorder) QueryLog(limit any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryLog", reflect.TypeOf((*MockClient)(nil).QueryLog), limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryLogConfig mocks base method.
|
||||||
|
func (m *MockClient) QueryLogConfig() (*model.QueryLogConfigWithIgnored, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "QueryLogConfig")
|
||||||
|
ret0, _ := ret[0].(*model.QueryLogConfigWithIgnored)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryLogConfig indicates an expected call of QueryLogConfig.
|
||||||
|
func (mr *MockClientMockRecorder) QueryLogConfig() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryLogConfig", reflect.TypeOf((*MockClient)(nil).QueryLogConfig))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefreshFilters mocks base method.
|
||||||
|
func (m *MockClient) RefreshFilters(whitelist bool) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "RefreshFilters", whitelist)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefreshFilters indicates an expected call of RefreshFilters.
|
||||||
|
func (mr *MockClientMockRecorder) RefreshFilters(whitelist any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RefreshFilters", reflect.TypeOf((*MockClient)(nil).RefreshFilters), whitelist)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RewriteList mocks base method.
|
||||||
|
func (m *MockClient) RewriteList() (*model.RewriteEntries, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "RewriteList")
|
||||||
|
ret0, _ := ret[0].(*model.RewriteEntries)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// RewriteList indicates an expected call of RewriteList.
|
||||||
|
func (mr *MockClientMockRecorder) RewriteList() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RewriteList", reflect.TypeOf((*MockClient)(nil).RewriteList))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SafeBrowsing mocks base method.
|
||||||
|
func (m *MockClient) SafeBrowsing() (bool, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "SafeBrowsing")
|
||||||
|
ret0, _ := ret[0].(bool)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// SafeBrowsing indicates an expected call of SafeBrowsing.
|
||||||
|
func (mr *MockClientMockRecorder) SafeBrowsing() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SafeBrowsing", reflect.TypeOf((*MockClient)(nil).SafeBrowsing))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SafeSearchConfig mocks base method.
|
||||||
|
func (m *MockClient) SafeSearchConfig() (*model.SafeSearchConfig, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "SafeSearchConfig")
|
||||||
|
ret0, _ := ret[0].(*model.SafeSearchConfig)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// SafeSearchConfig indicates an expected call of SafeSearchConfig.
|
||||||
|
func (mr *MockClientMockRecorder) SafeSearchConfig() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SafeSearchConfig", reflect.TypeOf((*MockClient)(nil).SafeSearchConfig))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAccessList mocks base method.
|
||||||
|
func (m *MockClient) SetAccessList(accessList *model.AccessList) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "SetAccessList", accessList)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAccessList indicates an expected call of SetAccessList.
|
||||||
|
func (mr *MockClientMockRecorder) SetAccessList(accessList any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAccessList", reflect.TypeOf((*MockClient)(nil).SetAccessList), accessList)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBlockedServicesSchedule mocks base method.
|
||||||
|
func (m *MockClient) SetBlockedServicesSchedule(schedule *model.BlockedServicesSchedule) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "SetBlockedServicesSchedule", schedule)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBlockedServicesSchedule indicates an expected call of SetBlockedServicesSchedule.
|
||||||
|
func (mr *MockClientMockRecorder) SetBlockedServicesSchedule(schedule any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBlockedServicesSchedule", reflect.TypeOf((*MockClient)(nil).SetBlockedServicesSchedule), schedule)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCustomRules mocks base method.
|
||||||
|
func (m *MockClient) SetCustomRules(rules *[]string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "SetCustomRules", rules)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCustomRules indicates an expected call of SetCustomRules.
|
||||||
|
func (mr *MockClientMockRecorder) SetCustomRules(rules any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCustomRules", reflect.TypeOf((*MockClient)(nil).SetCustomRules), rules)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDNSConfig mocks base method.
|
||||||
|
func (m *MockClient) SetDNSConfig(config *model.DNSConfig) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "SetDNSConfig", config)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDNSConfig indicates an expected call of SetDNSConfig.
|
||||||
|
func (mr *MockClientMockRecorder) SetDNSConfig(config any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDNSConfig", reflect.TypeOf((*MockClient)(nil).SetDNSConfig), config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDhcpConfig mocks base method.
|
||||||
|
func (m *MockClient) SetDhcpConfig(status *model.DhcpStatus) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "SetDhcpConfig", status)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDhcpConfig indicates an expected call of SetDhcpConfig.
|
||||||
|
func (mr *MockClientMockRecorder) SetDhcpConfig(status any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDhcpConfig", reflect.TypeOf((*MockClient)(nil).SetDhcpConfig), status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetProfileInfo mocks base method.
|
||||||
|
func (m *MockClient) SetProfileInfo(settings *model.ProfileInfo) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "SetProfileInfo", settings)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetProfileInfo indicates an expected call of SetProfileInfo.
|
||||||
|
func (mr *MockClientMockRecorder) SetProfileInfo(settings any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetProfileInfo", reflect.TypeOf((*MockClient)(nil).SetProfileInfo), settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetQueryLogConfig mocks base method.
|
||||||
|
func (m *MockClient) SetQueryLogConfig(ql *model.QueryLogConfigWithIgnored) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "SetQueryLogConfig", ql)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetQueryLogConfig indicates an expected call of SetQueryLogConfig.
|
||||||
|
func (mr *MockClientMockRecorder) SetQueryLogConfig(ql any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetQueryLogConfig", reflect.TypeOf((*MockClient)(nil).SetQueryLogConfig), ql)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSafeSearchConfig mocks base method.
|
||||||
|
func (m *MockClient) SetSafeSearchConfig(settings *model.SafeSearchConfig) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "SetSafeSearchConfig", settings)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSafeSearchConfig indicates an expected call of SetSafeSearchConfig.
|
||||||
|
func (mr *MockClientMockRecorder) SetSafeSearchConfig(settings any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSafeSearchConfig", reflect.TypeOf((*MockClient)(nil).SetSafeSearchConfig), settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStatsConfig mocks base method.
|
||||||
|
func (m *MockClient) SetStatsConfig(sc *model.PutStatsConfigUpdateRequest) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "SetStatsConfig", sc)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStatsConfig indicates an expected call of SetStatsConfig.
|
||||||
|
func (mr *MockClientMockRecorder) SetStatsConfig(sc any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetStatsConfig", reflect.TypeOf((*MockClient)(nil).SetStatsConfig), sc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTLSConfig mocks base method.
|
||||||
|
func (m *MockClient) SetTLSConfig(tls *model.TlsConfig) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "SetTLSConfig", tls)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTLSConfig indicates an expected call of SetTLSConfig.
|
||||||
|
func (mr *MockClientMockRecorder) SetTLSConfig(tls any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTLSConfig", reflect.TypeOf((*MockClient)(nil).SetTLSConfig), tls)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup mocks base method.
|
||||||
|
func (m *MockClient) Setup() error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Setup")
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup indicates an expected call of Setup.
|
||||||
|
func (mr *MockClientMockRecorder) Setup() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Setup", reflect.TypeOf((*MockClient)(nil).Setup))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stats mocks base method.
|
||||||
|
func (m *MockClient) Stats() (*model.Stats, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Stats")
|
||||||
|
ret0, _ := ret[0].(*model.Stats)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stats indicates an expected call of Stats.
|
||||||
|
func (mr *MockClientMockRecorder) Stats() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stats", reflect.TypeOf((*MockClient)(nil).Stats))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatsConfig mocks base method.
|
||||||
|
func (m *MockClient) StatsConfig() (*model.GetStatsConfigResponse, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "StatsConfig")
|
||||||
|
ret0, _ := ret[0].(*model.GetStatsConfigResponse)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatsConfig indicates an expected call of StatsConfig.
|
||||||
|
func (mr *MockClientMockRecorder) StatsConfig() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StatsConfig", reflect.TypeOf((*MockClient)(nil).StatsConfig))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status mocks base method.
|
||||||
|
func (m *MockClient) Status() (*model.ServerStatus, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Status")
|
||||||
|
ret0, _ := ret[0].(*model.ServerStatus)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status indicates an expected call of Status.
|
||||||
|
func (mr *MockClientMockRecorder) Status() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockClient)(nil).Status))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSConfig mocks base method.
|
||||||
|
func (m *MockClient) TLSConfig() (*model.TlsConfig, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "TLSConfig")
|
||||||
|
ret0, _ := ret[0].(*model.TlsConfig)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSConfig indicates an expected call of TLSConfig.
|
||||||
|
func (mr *MockClientMockRecorder) TLSConfig() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TLSConfig", reflect.TypeOf((*MockClient)(nil).TLSConfig))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToggleFiltering mocks base method.
|
||||||
|
func (m *MockClient) ToggleFiltering(enabled bool, interval int) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "ToggleFiltering", enabled, interval)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToggleFiltering indicates an expected call of ToggleFiltering.
|
||||||
|
func (mr *MockClientMockRecorder) ToggleFiltering(enabled, interval any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleFiltering", reflect.TypeOf((*MockClient)(nil).ToggleFiltering), enabled, interval)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToggleParental mocks base method.
|
||||||
|
func (m *MockClient) ToggleParental(enable bool) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "ToggleParental", enable)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToggleParental indicates an expected call of ToggleParental.
|
||||||
|
func (mr *MockClientMockRecorder) ToggleParental(enable any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleParental", reflect.TypeOf((*MockClient)(nil).ToggleParental), enable)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToggleProtection mocks base method.
|
||||||
|
func (m *MockClient) ToggleProtection(enable bool) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "ToggleProtection", enable)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToggleProtection indicates an expected call of ToggleProtection.
|
||||||
|
func (mr *MockClientMockRecorder) ToggleProtection(enable any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleProtection", reflect.TypeOf((*MockClient)(nil).ToggleProtection), enable)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToggleSafeBrowsing mocks base method.
|
||||||
|
func (m *MockClient) ToggleSafeBrowsing(enable bool) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "ToggleSafeBrowsing", enable)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToggleSafeBrowsing indicates an expected call of ToggleSafeBrowsing.
|
||||||
|
func (mr *MockClientMockRecorder) ToggleSafeBrowsing(enable any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleSafeBrowsing", reflect.TypeOf((*MockClient)(nil).ToggleSafeBrowsing), enable)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateClient mocks base method.
|
||||||
|
func (m *MockClient) UpdateClient(client *model.Client) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "UpdateClient", client)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateClient indicates an expected call of UpdateClient.
|
||||||
|
func (mr *MockClientMockRecorder) UpdateClient(client any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateClient", reflect.TypeOf((*MockClient)(nil).UpdateClient), client)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateFilter mocks base method.
|
||||||
|
func (m *MockClient) UpdateFilter(whitelist bool, f model.Filter) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "UpdateFilter", whitelist, f)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateFilter indicates an expected call of UpdateFilter.
|
||||||
|
func (mr *MockClientMockRecorder) UpdateFilter(whitelist, f any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateFilter", reflect.TypeOf((*MockClient)(nil).UpdateFilter), whitelist, f)
|
||||||
|
}
|
||||||
99
internal/mocks/flags/mock.go
Normal file
99
internal/mocks/flags/mock.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: github.com/bakito/adguardhome-sync/internal/config (interfaces: Flags)
|
||||||
|
//
|
||||||
|
// Generated by this command:
|
||||||
|
//
|
||||||
|
// mockgen -package client -destination internal/mocks/flags/mock.go github.com/bakito/adguardhome-sync/internal/config Flags
|
||||||
|
//
|
||||||
|
|
||||||
|
// Package client is a generated GoMock package.
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
gomock "go.uber.org/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockFlags is a mock of Flags interface.
|
||||||
|
type MockFlags struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockFlagsMockRecorder
|
||||||
|
isgomock struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockFlagsMockRecorder is the mock recorder for MockFlags.
|
||||||
|
type MockFlagsMockRecorder struct {
|
||||||
|
mock *MockFlags
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockFlags creates a new mock instance.
|
||||||
|
func NewMockFlags(ctrl *gomock.Controller) *MockFlags {
|
||||||
|
mock := &MockFlags{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockFlagsMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockFlags) EXPECT() *MockFlagsMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Changed mocks base method.
|
||||||
|
func (m *MockFlags) Changed(name string) bool {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Changed", name)
|
||||||
|
ret0, _ := ret[0].(bool)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Changed indicates an expected call of Changed.
|
||||||
|
func (mr *MockFlagsMockRecorder) Changed(name any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Changed", reflect.TypeOf((*MockFlags)(nil).Changed), name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBool mocks base method.
|
||||||
|
func (m *MockFlags) GetBool(name string) (bool, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetBool", name)
|
||||||
|
ret0, _ := ret[0].(bool)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBool indicates an expected call of GetBool.
|
||||||
|
func (mr *MockFlagsMockRecorder) GetBool(name any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBool", reflect.TypeOf((*MockFlags)(nil).GetBool), name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt mocks base method.
|
||||||
|
func (m *MockFlags) GetInt(name string) (int, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetInt", name)
|
||||||
|
ret0, _ := ret[0].(int)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt indicates an expected call of GetInt.
|
||||||
|
func (mr *MockFlagsMockRecorder) GetInt(name any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInt", reflect.TypeOf((*MockFlags)(nil).GetInt), name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetString mocks base method.
|
||||||
|
func (m *MockFlags) GetString(name string) (string, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetString", name)
|
||||||
|
ret0, _ := ret[0].(string)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetString indicates an expected call of GetString.
|
||||||
|
func (mr *MockFlagsMockRecorder) GetString(name any) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetString", reflect.TypeOf((*MockFlags)(nil).GetString), name)
|
||||||
|
}
|
||||||
300
internal/sync/action-general.go
Normal file
300
internal/sync/action-general.go
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
package sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
actionProfileInfo = func(ac *actionContext) error {
|
||||||
|
if pro, err := ac.client.ProfileInfo(); err != nil {
|
||||||
|
return err
|
||||||
|
} else if merged := pro.ShouldSyncFor(ac.origin.profileInfo, ac.cfg.Features.Theme); merged != nil {
|
||||||
|
return ac.client.SetProfileInfo(merged)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
actionProtection = func(ac *actionContext) error {
|
||||||
|
if ac.origin.status.ProtectionEnabled != ac.replicaStatus.ProtectionEnabled {
|
||||||
|
return ac.client.ToggleProtection(ac.origin.status.ProtectionEnabled)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
actionParental = func(ac *actionContext) error {
|
||||||
|
if rp, err := ac.client.Parental(); err != nil {
|
||||||
|
return err
|
||||||
|
} else if ac.origin.parental != rp {
|
||||||
|
return ac.client.ToggleParental(ac.origin.parental)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
actionSafeSearchConfig = func(ac *actionContext) error {
|
||||||
|
if ssc, err := ac.client.SafeSearchConfig(); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !ac.origin.safeSearch.Equals(ssc) {
|
||||||
|
return ac.client.SetSafeSearchConfig(ac.origin.safeSearch)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
actionSafeBrowsing = func(ac *actionContext) error {
|
||||||
|
if rs, err := ac.client.SafeBrowsing(); err != nil {
|
||||||
|
return err
|
||||||
|
} else if ac.origin.safeBrowsing != rs {
|
||||||
|
if err = ac.client.ToggleSafeBrowsing(ac.origin.safeBrowsing); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
actionQueryLogConfig = func(ac *actionContext) error {
|
||||||
|
qlc, err := ac.client.QueryLogConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !ac.origin.queryLogConfig.Equals(qlc) {
|
||||||
|
return ac.client.SetQueryLogConfig(ac.origin.queryLogConfig)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
actionStatsConfig = func(ac *actionContext) error {
|
||||||
|
sc, err := ac.client.StatsConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !sc.Equals(ac.origin.statsConfig) {
|
||||||
|
return ac.client.SetStatsConfig(ac.origin.statsConfig)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
actionDNSRewrites = func(ac *actionContext) error {
|
||||||
|
replicaRewrites, err := ac.client.RewriteList()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a, r, d := replicaRewrites.Merge(ac.origin.rewrites)
|
||||||
|
|
||||||
|
if err = ac.client.DeleteRewriteEntries(r...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = ac.client.AddRewriteEntries(a...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dupl := range d {
|
||||||
|
ac.rl.With("domain", dupl.Domain, "answer", dupl.Answer).Warn("Skipping duplicated rewrite from source")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
actionFilters = func(ac *actionContext) error {
|
||||||
|
rf, err := ac.client.Filtering()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = syncFilterType(ac.rl, ac.origin.filters.Filters, rf.Filters, false, ac.client, ac.cfg.ContinueOnError); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = syncFilterType(ac.rl, ac.origin.filters.WhitelistFilters, rf.WhitelistFilters, true, ac.client, ac.cfg.ContinueOnError); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if utils.PtrToString(ac.origin.filters.UserRules) != utils.PtrToString(rf.UserRules) {
|
||||||
|
return ac.client.SetCustomRules(ac.origin.filters.UserRules)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !utils.PtrEquals(ac.origin.filters.Enabled, rf.Enabled) ||
|
||||||
|
!utils.PtrEquals(ac.origin.filters.Interval, rf.Interval) {
|
||||||
|
return ac.client.ToggleFiltering(*ac.origin.filters.Enabled, *ac.origin.filters.Interval)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
actionBlockedServicesSchedule = func(ac *actionContext) error {
|
||||||
|
rbss, err := ac.client.BlockedServicesSchedule()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ac.origin.blockedServicesSchedule.Equals(rbss) {
|
||||||
|
return ac.client.SetBlockedServicesSchedule(ac.origin.blockedServicesSchedule)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
actionClientSettings = func(ac *actionContext) error {
|
||||||
|
rc, err := ac.client.Clients()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a, u, r := rc.Merge(ac.origin.clients)
|
||||||
|
|
||||||
|
for _, client := range r {
|
||||||
|
if err := ac.client.DeleteClient(client); err != nil {
|
||||||
|
ac.rl.With("client-name", client.Name, "error", err).Error("error deleting client setting")
|
||||||
|
if !ac.cfg.ContinueOnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, client := range a {
|
||||||
|
if err := ac.client.AddClient(client); err != nil {
|
||||||
|
ac.rl.With("client-name", client.Name, "error", err).Error("error adding client setting")
|
||||||
|
if !ac.cfg.ContinueOnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, client := range u {
|
||||||
|
if err := ac.client.UpdateClient(client); err != nil {
|
||||||
|
ac.rl.With("client-name", client.Name, "error", err).Error("error updating client setting")
|
||||||
|
if !ac.cfg.ContinueOnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
actionDNSAccessLists = func(ac *actionContext) error {
|
||||||
|
al, err := ac.client.AccessList()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !al.Equals(ac.origin.accessList) {
|
||||||
|
return ac.client.SetAccessList(ac.origin.accessList)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
actionDNSServerConfig = func(ac *actionContext) error {
|
||||||
|
dc, err := ac.client.DNSConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// dc.Sanitize(ac.rl)
|
||||||
|
|
||||||
|
if !dc.Equals(ac.origin.dnsConfig) {
|
||||||
|
if err = ac.client.SetDNSConfig(ac.origin.dnsConfig); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
actionDHCPServerConfig = func(ac *actionContext) error {
|
||||||
|
if ac.origin.dhcpServerConfig.HasConfig() {
|
||||||
|
sc, err := ac.client.DhcpConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
origClone := ac.origin.dhcpServerConfig.Clone()
|
||||||
|
if ac.replica.InterfaceName != "" {
|
||||||
|
// overwrite interface name
|
||||||
|
origClone.InterfaceName = utils.Ptr(ac.replica.InterfaceName)
|
||||||
|
}
|
||||||
|
if ac.replica.DHCPServerEnabled != nil {
|
||||||
|
// overwrite dhcp enabled
|
||||||
|
origClone.Enabled = ac.replica.DHCPServerEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sc.CleanAndEquals(origClone) {
|
||||||
|
return ac.client.SetDhcpConfig(origClone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
actionDHCPStaticLeases = func(ac *actionContext) error {
|
||||||
|
sc, err := ac.client.DhcpConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a, r := model.MergeDhcpStaticLeases(sc.StaticLeases, ac.origin.dhcpServerConfig.StaticLeases)
|
||||||
|
|
||||||
|
for _, lease := range r {
|
||||||
|
if err := ac.client.DeleteDHCPStaticLease(lease); err != nil {
|
||||||
|
ac.rl.With("hostname", lease.Hostname, "error", err).Error("error deleting dhcp static lease")
|
||||||
|
if !ac.cfg.ContinueOnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, lease := range a {
|
||||||
|
if err := ac.client.AddDHCPStaticLease(lease); err != nil {
|
||||||
|
ac.rl.With("hostname", lease.Hostname, "error", err).Error("error adding dhcp static lease")
|
||||||
|
if !ac.cfg.ContinueOnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
tlsConfig = func(ac *actionContext) error {
|
||||||
|
tlsc, err := ac.client.TLSConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tlsc.Equals(ac.origin.tlsConfig) {
|
||||||
|
if err := ac.client.SetTLSConfig(ac.origin.tlsConfig); err != nil {
|
||||||
|
ac.rl.With("enabled", ac.origin.tlsConfig.Enabled, "error", err).Error("error setting tls config")
|
||||||
|
if !ac.cfg.ContinueOnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func syncFilterType(
|
||||||
|
rl *zap.SugaredLogger,
|
||||||
|
of *[]model.Filter,
|
||||||
|
rFilters *[]model.Filter,
|
||||||
|
whitelist bool,
|
||||||
|
replica client.Client,
|
||||||
|
continueOnError bool,
|
||||||
|
) error {
|
||||||
|
fa, fu, fd := model.MergeFilters(rFilters, of)
|
||||||
|
|
||||||
|
for _, f := range fd {
|
||||||
|
if err := replica.DeleteFilter(whitelist, f); err != nil {
|
||||||
|
rl.With("filter", f.Name, "url", f.Url, "whitelist", whitelist, "error", err).Error("error deleting filter")
|
||||||
|
if !continueOnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range fa {
|
||||||
|
if err := replica.AddFilter(whitelist, f); err != nil {
|
||||||
|
rl.With("filter", f.Name, "url", f.Url, "whitelist", whitelist, "error", err).Error("error adding filter")
|
||||||
|
if !continueOnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range fu {
|
||||||
|
if err := replica.UpdateFilter(whitelist, f); err != nil {
|
||||||
|
rl.With("filter", f.Name, "url", f.Url, "whitelist", whitelist, "error", err).Error("error updating filter")
|
||||||
|
if !continueOnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fa) > 0 || len(fu) > 0 {
|
||||||
|
if err := replica.RefreshFilters(whitelist); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
108
internal/sync/action.go
Normal file
108
internal/sync/action.go
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupActions(cfg *types.Config) (actions []syncAction) {
|
||||||
|
if cfg.Features.GeneralSettings {
|
||||||
|
actions = append(actions,
|
||||||
|
action("profile info", actionProfileInfo),
|
||||||
|
action("protection", actionProtection),
|
||||||
|
action("parental", actionParental),
|
||||||
|
action("safe search config", actionSafeSearchConfig),
|
||||||
|
action("safe browsing", actionSafeBrowsing),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if cfg.Features.DNS.ServerConfig {
|
||||||
|
actions = append(actions,
|
||||||
|
action("DNS server config", actionDNSServerConfig),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if cfg.Features.QueryLogConfig {
|
||||||
|
actions = append(actions,
|
||||||
|
action("query log config", actionQueryLogConfig),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if cfg.Features.StatsConfig {
|
||||||
|
actions = append(actions,
|
||||||
|
action("stats config", actionStatsConfig),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if cfg.Features.DNS.Rewrites {
|
||||||
|
actions = append(actions,
|
||||||
|
action("DNS rewrites", actionDNSRewrites),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if cfg.Features.Filters {
|
||||||
|
actions = append(actions,
|
||||||
|
action("actionFilters", actionFilters),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if cfg.Features.Services {
|
||||||
|
actions = append(actions,
|
||||||
|
action("blocked services schedule", actionBlockedServicesSchedule),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if cfg.Features.ClientSettings {
|
||||||
|
actions = append(actions,
|
||||||
|
action("client settings", actionClientSettings),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if cfg.Features.DNS.AccessLists {
|
||||||
|
actions = append(actions,
|
||||||
|
action("DNS access lists", actionDNSAccessLists),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if cfg.Features.DHCP.ServerConfig {
|
||||||
|
actions = append(actions,
|
||||||
|
action("DHCP server config", actionDHCPServerConfig),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if cfg.Features.DHCP.StaticLeases {
|
||||||
|
actions = append(actions,
|
||||||
|
action("DHCP static leases", actionDHCPStaticLeases),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if cfg.Features.TLSConfig {
|
||||||
|
actions = append(actions,
|
||||||
|
action("TLS config", tlsConfig),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return actions
|
||||||
|
}
|
||||||
|
|
||||||
|
type syncAction interface {
|
||||||
|
sync(ac *actionContext) error
|
||||||
|
name() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type actionContext struct {
|
||||||
|
rl *zap.SugaredLogger
|
||||||
|
origin *origin
|
||||||
|
client client.Client
|
||||||
|
replicaStatus *model.ServerStatus
|
||||||
|
replica types.AdGuardInstance
|
||||||
|
cfg *types.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
type defaultAction struct {
|
||||||
|
myName string
|
||||||
|
doSync func(ac *actionContext) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func action(name string, f func(ac *actionContext) error) syncAction {
|
||||||
|
return &defaultAction{myName: name, doSync: f}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *defaultAction) sync(ac *actionContext) error {
|
||||||
|
return d.doSync(ac)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *defaultAction) name() string {
|
||||||
|
return d.myName
|
||||||
|
}
|
||||||
222
internal/sync/http.go
Normal file
222
internal/sync/http.go
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
package sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
_ "embed"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/log"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/metrics"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/sync/static"
|
||||||
|
"github.com/bakito/adguardhome-sync/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (w *worker) handleSync(c *gin.Context) {
|
||||||
|
l.With("remote-addr", c.Request.RemoteAddr).Info("Starting sync from API")
|
||||||
|
w.sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *worker) handleRoot(c *gin.Context) {
|
||||||
|
total, dns, blocked, malware, adult := metrics.StatsGraph()
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "index.html", map[string]any{
|
||||||
|
"DarkMode": w.cfg.API.DarkMode,
|
||||||
|
"Metrics": w.cfg.API.Metrics.Enabled,
|
||||||
|
"Version": version.Version,
|
||||||
|
"Build": version.Build,
|
||||||
|
"SyncStatus": w.status(),
|
||||||
|
"Stats": map[string]any{
|
||||||
|
"Labels": getLast24Hours(),
|
||||||
|
"DNS": dns,
|
||||||
|
"Blocked": blocked,
|
||||||
|
"BlockedPercentage": percent(total.NumBlockedFiltering, total.NumDnsQueries),
|
||||||
|
"Malware": malware,
|
||||||
|
"MalwarePercentage": percent(total.NumReplacedSafebrowsing, total.NumDnsQueries),
|
||||||
|
"Adult": adult,
|
||||||
|
"AdultPercentage": percent(total.NumReplacedParental, total.NumDnsQueries),
|
||||||
|
"TotalDNS": total.NumDnsQueries,
|
||||||
|
"TotalBlocked": total.NumBlockedFiltering,
|
||||||
|
"TotalMalware": total.NumReplacedSafebrowsing,
|
||||||
|
"TotalAdult": total.NumReplacedParental,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func percent(a, b *int) string {
|
||||||
|
if a == nil || b == nil || *b == 0 {
|
||||||
|
return "0.00"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%.2f", (float64(*a)*100.0)/float64(*b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *worker) handleLogs(c *gin.Context) {
|
||||||
|
c.Data(http.StatusOK, "text/plain", []byte(strings.Join(log.Logs(), "")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *worker) handleClearLogs(c *gin.Context) {
|
||||||
|
log.Clear()
|
||||||
|
c.Status(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *worker) handleStatus(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, w.status())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *worker) handleHealthz(c *gin.Context) {
|
||||||
|
status := w.status()
|
||||||
|
|
||||||
|
if status.Origin.Status != "success" {
|
||||||
|
c.Status(http.StatusServiceUnavailable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, replica := range status.Replicas {
|
||||||
|
if replica.Status != "success" {
|
||||||
|
c.Status(http.StatusServiceUnavailable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Status(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *worker) listenAndServe() {
|
||||||
|
sl := l.With("port", w.cfg.API.Port)
|
||||||
|
if w.cfg.API.TLS.Enabled() {
|
||||||
|
c, k := w.cfg.API.TLS.Certs()
|
||||||
|
sl = sl.With("tls-cert", c).With("tls-key", k)
|
||||||
|
}
|
||||||
|
sl.Info("Starting API server")
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
r := gin.New()
|
||||||
|
r.Use(gin.Recovery())
|
||||||
|
|
||||||
|
r.HEAD("/healthz", w.handleHealthz)
|
||||||
|
r.GET("/healthz", w.handleHealthz)
|
||||||
|
|
||||||
|
var group gin.IRouter = r
|
||||||
|
if w.cfg.API.Username != "" && w.cfg.API.Password != "" {
|
||||||
|
group = r.Group("/", gin.BasicAuth(map[string]string{w.cfg.API.Username: w.cfg.API.Password}))
|
||||||
|
}
|
||||||
|
|
||||||
|
group.POST("/api/v1/sync", w.handleSync)
|
||||||
|
group.GET("/api/v1/logs", w.handleLogs)
|
||||||
|
group.POST("/api/v1/clear-logs", w.handleClearLogs)
|
||||||
|
group.GET("/api/v1/status", w.handleStatus)
|
||||||
|
static.HandleResources(group, w.cfg.API.DarkMode)
|
||||||
|
group.GET("/", w.handleRoot)
|
||||||
|
if w.cfg.API.Metrics.Enabled {
|
||||||
|
group.GET("/metrics", metrics.Handler())
|
||||||
|
|
||||||
|
go w.startScraping()
|
||||||
|
}
|
||||||
|
|
||||||
|
httpServer := &http.Server{
|
||||||
|
Addr: fmt.Sprintf(":%d", w.cfg.API.Port),
|
||||||
|
Handler: r,
|
||||||
|
BaseContext: func(_ net.Listener) context.Context { return ctx },
|
||||||
|
ReadHeaderTimeout: 1 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
r.SetHTMLTemplate(template.Must(template.New("index.html").Parse(static.Index())))
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
var err error
|
||||||
|
if w.cfg.API.TLS.Enabled() {
|
||||||
|
err = httpServer.ListenAndServeTLS(w.cfg.API.TLS.Certs())
|
||||||
|
} else {
|
||||||
|
err = httpServer.ListenAndServe()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
l.With("error", err).Fatalf("HTTP server ListenAndServe")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
signalChan := make(chan os.Signal, 1)
|
||||||
|
|
||||||
|
signal.Notify(
|
||||||
|
signalChan,
|
||||||
|
syscall.SIGHUP, // kill -SIGHUP XXXX
|
||||||
|
syscall.SIGINT, // kill -SIGINT XXXX or Ctrl+c
|
||||||
|
syscall.SIGQUIT, // kill -SIGQUIT XXXX
|
||||||
|
)
|
||||||
|
|
||||||
|
<-signalChan
|
||||||
|
l.Info("os.Interrupt - shutting down...")
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-signalChan
|
||||||
|
l.Fatal("os.Kill - terminating...")
|
||||||
|
}()
|
||||||
|
|
||||||
|
gracefulCtx, cancelShutdown := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancelShutdown()
|
||||||
|
|
||||||
|
if w.cron != nil {
|
||||||
|
l.Info("Stopping cron")
|
||||||
|
w.cron.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := httpServer.Shutdown(gracefulCtx); err != nil {
|
||||||
|
l.With("error", err).Error("Shutdown error")
|
||||||
|
defer os.Exit(1)
|
||||||
|
} else {
|
||||||
|
l.Info("API server stopped")
|
||||||
|
}
|
||||||
|
|
||||||
|
// manually cancel context if not using httpServer.RegisterOnShutdown(cancel)
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
type syncStatus struct {
|
||||||
|
SyncRunning bool `json:"syncRunning"`
|
||||||
|
Origin replicaStatus `json:"origin"`
|
||||||
|
Replicas []replicaStatus `json:"replicas"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type replicaStatus struct {
|
||||||
|
Host string `json:"host"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
ProtectionEnabled *bool `json:"protection_enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLast24Hours() []string {
|
||||||
|
var result []string
|
||||||
|
currentTime := time.Now()
|
||||||
|
|
||||||
|
// Loop to get the last 24 hours
|
||||||
|
for i := range 24 {
|
||||||
|
// Calculate the time for the current hour in the loop
|
||||||
|
timeInstance := currentTime.Add(time.Duration(-i) * time.Hour)
|
||||||
|
timeInstance = timeInstance.Truncate(time.Hour)
|
||||||
|
|
||||||
|
// Format the time as "14 Dec 17:00"
|
||||||
|
formattedTime := timeInstance.Format("02 Jan 15:04")
|
||||||
|
result = append(result, formattedTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse the slice to get the correct order (from oldest to latest)
|
||||||
|
for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
result[i], result[j] = result[j], result[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
30
internal/sync/http_test.go
Normal file
30
internal/sync/http_test.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Percent", func() {
|
||||||
|
DescribeTable("calculating percentage",
|
||||||
|
func(a, b *int, want string) {
|
||||||
|
Expect(percent(a, b)).To(Equal(want))
|
||||||
|
},
|
||||||
|
Entry("both inputs are nil", nil, nil, "0.00"),
|
||||||
|
Entry("a is nil, b is non-zero", nil, intPtr(10), "0.00"),
|
||||||
|
Entry("b is nil, a is non-zero", intPtr(10), nil, "0.00"),
|
||||||
|
Entry("b is zero", intPtr(10), intPtr(0), "0.00"),
|
||||||
|
Entry("normal case with positive int values", intPtr(25), intPtr(100), "25.00"),
|
||||||
|
Entry("a and b are equal", intPtr(50), intPtr(50), "100.00"),
|
||||||
|
Entry("a is zero, b is positive", intPtr(0), intPtr(50), "0.00"),
|
||||||
|
Entry("large positive values", intPtr(1000), intPtr(4000), "25.00"),
|
||||||
|
Entry("a greater than b", intPtr(150), intPtr(100), "150.00"),
|
||||||
|
Entry("negative values for a and b", intPtr(-25), intPtr(-50), "50.00"),
|
||||||
|
Entry("a is positive, b is negative", intPtr(25), intPtr(-50), "-50.00"),
|
||||||
|
Entry("a is negative, b is positive", intPtr(-25), intPtr(50), "-50.00"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
func intPtr(i int) *int {
|
||||||
|
return &i
|
||||||
|
}
|
||||||
51
internal/sync/scrape.go
Normal file
51
internal/sync/scrape.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/metrics"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (w *worker) startScraping() {
|
||||||
|
metrics.Init()
|
||||||
|
if w.cfg.API.Metrics.ScrapeInterval == 0 {
|
||||||
|
w.cfg.API.Metrics.ScrapeInterval = 30 * time.Second
|
||||||
|
}
|
||||||
|
if w.cfg.API.Metrics.QueryLogLimit == 0 {
|
||||||
|
w.cfg.API.Metrics.QueryLogLimit = 10_000
|
||||||
|
}
|
||||||
|
l.With(
|
||||||
|
"scrape-interval", w.cfg.API.Metrics.ScrapeInterval,
|
||||||
|
"query-log-limit", w.cfg.API.Metrics.QueryLogLimit,
|
||||||
|
).Info("setup metrics")
|
||||||
|
w.scrape()
|
||||||
|
for range time.Tick(w.cfg.API.Metrics.ScrapeInterval) {
|
||||||
|
w.scrape()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *worker) scrape() {
|
||||||
|
var iml metrics.InstanceMetricsList
|
||||||
|
|
||||||
|
iml.Metrics = append(iml.Metrics, w.getMetrics(*w.cfg.Origin))
|
||||||
|
for _, replica := range w.cfg.Replicas {
|
||||||
|
iml.Metrics = append(iml.Metrics, w.getMetrics(replica))
|
||||||
|
}
|
||||||
|
metrics.UpdateInstances(iml)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *worker) getMetrics(inst types.AdGuardInstance) metrics.InstanceMetrics {
|
||||||
|
var im metrics.InstanceMetrics
|
||||||
|
client, err := w.createClient(inst)
|
||||||
|
if err != nil {
|
||||||
|
l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client")
|
||||||
|
return im
|
||||||
|
}
|
||||||
|
|
||||||
|
im.HostName = inst.Host
|
||||||
|
im.Status, _ = client.Status()
|
||||||
|
im.Stats, _ = client.Stats()
|
||||||
|
im.QueryLog, _ = client.QueryLog(w.cfg.API.Metrics.QueryLogLimit)
|
||||||
|
return im
|
||||||
|
}
|
||||||
6
internal/sync/static/bootstrap.min-5.3.3.css
vendored
Normal file
6
internal/sync/static/bootstrap.min-5.3.3.css
vendored
Normal file
File diff suppressed because one or more lines are too long
7
internal/sync/static/bootstrap.min-5.3.3.js
vendored
Normal file
7
internal/sync/static/bootstrap.min-5.3.3.js
vendored
Normal file
File diff suppressed because one or more lines are too long
12
internal/sync/static/bootstrap.min-darkly-5.3.css
vendored
Normal file
12
internal/sync/static/bootstrap.min-darkly-5.3.css
vendored
Normal file
File diff suppressed because one or more lines are too long
20
internal/sync/static/chart.umd.min-4.4.7.js
Normal file
20
internal/sync/static/chart.umd.min-4.4.7.js
Normal file
File diff suppressed because one or more lines are too long
BIN
internal/sync/static/favicon.ico
Normal file
BIN
internal/sync/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
242
internal/sync/static/index.html
Normal file
242
internal/sync/static/index.html
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>AdGuard Home sync</title>
|
||||||
|
<script type="text/javascript" src="lib/jquery.js"></script>
|
||||||
|
<link rel="stylesheet" href="lib/bootstrap.css">
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function () {
|
||||||
|
$("#showLogs").click(function () {
|
||||||
|
$.get("api/v1/logs", {}, function (data) {
|
||||||
|
$('#logs').html(data);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
$.get("api/v1/status", {}, function (status) {
|
||||||
|
$('#origin').removeClass(function (index, className) {
|
||||||
|
return (className.match(/(^|\s)btn-\S+/g) || []).join(' ');
|
||||||
|
}).addClass("btn-" + status.origin.status).attr('title', status.origin.error);
|
||||||
|
status.replicas.forEach(function (replica, i) {
|
||||||
|
$('#replica_' + i).removeClass(function (index, className) {
|
||||||
|
return (className.match(/(^|\s)btn-\S+/g) || []).join(' ');
|
||||||
|
}).addClass("btn-" + replica.status).attr('title', replica.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
$("#clearLogs").click(function () {
|
||||||
|
$.post("api/v1/clear-logs", {}, function () {
|
||||||
|
$('#logs').html("");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
$("#sync").click(function () {
|
||||||
|
$.post("api/v1/sync", {}, function (data) {
|
||||||
|
});
|
||||||
|
$("#showLogs").click();
|
||||||
|
});
|
||||||
|
$("#showLogs").click();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<link rel="shortcut icon" href="favicon.ico">
|
||||||
|
<style>
|
||||||
|
.stat-card {
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
|
||||||
|
padding: 15px;
|
||||||
|
text-align: left;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
{{- if .Metrics }}
|
||||||
|
.stat-card h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
.stat-card p {
|
||||||
|
margin: 5px 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
.percentage {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
text-align: right;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
flex-grow: 1;
|
||||||
|
height: 100px !important;
|
||||||
|
}
|
||||||
|
{{- end }}
|
||||||
|
.button-row {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.btn-group {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container-fluid px-4">
|
||||||
|
<div class="row">
|
||||||
|
<div class="d-flex align-items-center mb-3">
|
||||||
|
<img src="logo.svg" alt="Logo" class="me-3" style="height: 4em;">
|
||||||
|
<div>
|
||||||
|
<h1 class="mb-0">AdGuard Home sync</h1>
|
||||||
|
<p class="h6 text-muted mb-0">{{ .Version }} ({{ .Build }})</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{- if .Metrics }}
|
||||||
|
<div class="row g-4 d-flex">
|
||||||
|
<div class="col-12 col-md-3 d-flex">
|
||||||
|
<div class="stat-card flex-fill">
|
||||||
|
<div class="percentage"></div>
|
||||||
|
<h3 style="color: rgb(78, 141, 245);">{{.Stats.TotalDNS}}</h3>
|
||||||
|
<p>DNS Queries</p>
|
||||||
|
<canvas id="dnsQueriesChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-md-3 d-flex">
|
||||||
|
<div class="stat-card flex-fill">
|
||||||
|
<div class="percentage" style="color: rgb(255, 94, 94);">{{.Stats.BlockedPercentage}}%</div>
|
||||||
|
<h3 style="color: rgb(255, 94, 94);">{{.Stats.TotalBlocked}}</h3>
|
||||||
|
<p>Blocked by Filters</p>
|
||||||
|
<canvas id="blockedFiltersChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-md-3 d-flex">
|
||||||
|
<div class="stat-card flex-fill">
|
||||||
|
<div class="percentage" style="color: rgb(110, 224, 122);">{{.Stats.MalwarePercentage}}%</div>
|
||||||
|
<h3 style="color: rgb(110, 224, 122);">{{.Stats.TotalMalware}}</h3>
|
||||||
|
<p>Blocked malware/phishing</p>
|
||||||
|
<canvas id="malwareChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-md-3 d-flex">
|
||||||
|
<div class="stat-card flex-fill">
|
||||||
|
<div class="percentage" style="color: rgb(232, 198, 78);">{{.Stats.AdultPercentage}}%</div>
|
||||||
|
<h3 style="color: rgb(232, 198, 78);">{{.Stats.TotalAdult}}</h3>
|
||||||
|
<p>Blocked adult websites</p>
|
||||||
|
<canvas id="adultWebsitesChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{- end }}
|
||||||
|
<div class="row button-row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="btn-group" role="group">
|
||||||
|
<button type="button" class="btn btn-success" id="sync">Synchronize</button>
|
||||||
|
<button type="button" class="btn btn-secondary" id="showLogs">Update Logs</button>
|
||||||
|
<button type="button" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu">
|
||||||
|
<a class="dropdown-item" href="#" id="clearLogs">Clear Logs</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col col-md-auto">
|
||||||
|
<div class="btn-group float-right" role="group">
|
||||||
|
<a href="{{ .SyncStatus.Origin.URL }}" target="_blank" class="btn btn-{{ .SyncStatus.Origin.Status }}"
|
||||||
|
type="button" id="origin"
|
||||||
|
{{ if .SyncStatus.Origin.Error }} title="{{ .SyncStatus.Origin.Error }}" {{ end }}>Origin {{ .SyncStatus.Origin.Host }}</a>
|
||||||
|
{{ range $i, $r := .SyncStatus.Replicas }}
|
||||||
|
<a href="{{ $r.URL }}" target="_blank" class="btn btn-{{ $r.Status }}"
|
||||||
|
type="button" id="replica_{{ $i }}"
|
||||||
|
{{ if $r.Error }} title="{{ $r.Error }}" {{ end }} >Replica {{ $r.Host }}</a>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-12 col-md-12">
|
||||||
|
<div class="stat-card">
|
||||||
|
<pre class="p-3 border"><code id="logs"></code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- openssl dgst -sha384 -binary popper.min.js | openssl base64 -A -->
|
||||||
|
<script src="lib/popper.js" ></script>
|
||||||
|
<script src="lib/bootstrap.js" ></script>
|
||||||
|
{{- if .Metrics }}
|
||||||
|
<script src="lib/chart.js" ></script>
|
||||||
|
<script>
|
||||||
|
// Function to create minimal line charts
|
||||||
|
function createChart(canvasId, data) {
|
||||||
|
const ctx = document.getElementById(canvasId).getContext('2d');
|
||||||
|
|
||||||
|
const datasets = Array(data.length);
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
datasets[i] = {
|
||||||
|
data: data[i].data,
|
||||||
|
title: data[i].title,
|
||||||
|
backgroundColor: `rgb(${data[i].r}, ${data[i].g}, ${data[i].b}, 0.2)`,
|
||||||
|
borderColor: `rgb(${data[i].r}, ${data[i].g}, ${data[i].b}, 1)`,
|
||||||
|
borderWidth: 3,
|
||||||
|
fill: data[i].fill,
|
||||||
|
pointRadius: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: {{.Stats.Labels}},
|
||||||
|
datasets: datasets
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
interaction: {
|
||||||
|
mode: 'index',
|
||||||
|
intersect: false,
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: { display: false },
|
||||||
|
tooltip: {
|
||||||
|
enabled: true,
|
||||||
|
bodyFont: {
|
||||||
|
size: 20
|
||||||
|
},
|
||||||
|
titleFont: {
|
||||||
|
size: 20
|
||||||
|
},
|
||||||
|
displayColors: false,
|
||||||
|
callbacks: {
|
||||||
|
label: function(tooltipItem) {
|
||||||
|
if (tooltipItem.dataset.title) {
|
||||||
|
return tooltipItem.raw + " - " + tooltipItem.dataset.title;
|
||||||
|
}
|
||||||
|
return tooltipItem.raw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: { display: false,
|
||||||
|
title: {
|
||||||
|
display: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
y: { display: false,
|
||||||
|
min: 0,
|
||||||
|
title: {
|
||||||
|
display: true
|
||||||
|
} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createChart('dnsQueriesChart', {{.Stats.DNS}});
|
||||||
|
createChart('blockedFiltersChart', {{.Stats.Blocked}});
|
||||||
|
createChart('malwareChart', {{.Stats.Malware}});
|
||||||
|
createChart('adultWebsitesChart', {{.Stats.Adult}});
|
||||||
|
</script>
|
||||||
|
{{- end }}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
2
internal/sync/static/jquery-3.7.1.min.js
vendored
Normal file
2
internal/sync/static/jquery-3.7.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
internal/sync/static/logo.svg
Normal file
1
internal/sync/static/logo.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" id="svg3" version="1.2" viewBox="0 0 1000 1000"><defs id="defs3"><linearGradient id="swatch26"><stop style="stop-color:#000;stop-opacity:1" id="stop26" offset="0"/></linearGradient><linearGradient id="swatch25"><stop style="stop-color:#407b28;stop-opacity:1" id="stop25" offset="0"/></linearGradient></defs><path id="path1" fill="#68bc71" d="m 993.75002,114.1 c 0,171.8 3.1,595.3 -493.8,885.9 C 3.0500233,709.4 6.2500233,285.9 6.2500233,114.1 159.35002,35.9 345.25002,0 499.95002,0 c 154.7,0 340.6,35.9 493.8,114.1 z"/><path id="path2" fill="#67b279" d="M500 1000C3.1 709.4 6.2 285.9 6.2 114.1 159.4 35.9 345.3 0 500 0z"/><path id="path3" fill="#fff" d="m 225,449.6 c 15,-11.7 80,-53.4 128.3,1.6 L 453.3,569.6 720,297.9 c 11.7,-10 31.7,-23.3 55,-5 L 455,716.2 Z" style="display:none"/><circle style="display:none;fill:#fff;fill-opacity:.5;stroke:#fff;stroke-width:8e-08;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:100;stroke-dasharray:none;stroke-opacity:.502604" id="path18" cx="-500" cy="426" r="400" transform="scale(-1,1)"/><path id="path12-8" style="fill:#fff;fill-opacity:1;stroke:#fff;stroke-width:4.00001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:100;stroke-dasharray:none;stroke-opacity:1" d="m -75.867249,555.75073 c -1.469322,-39.92741 -12.296964,-76.43624 -30.823861,-104.61661 -21.05517,28.18037 -47.89663,45.38022 -75.18829,53.68483 l 42.51192,20.42415 c -33.35434,61.94297 -101.60764,105.3289 -167.51689,101.23666 -42.42317,-1.43193 -82.3318,-22.18867 -112.01355,-52.33871 -21.09944,-21.43232 -53.37423,-31.44115 -79.38397,-16.1329 l -10.3958,6.11853 c 43.49744,68.86447 120.54256,113.80911 201.79332,112.04834 87.97224,-4.41047 162.60077,-62.73987 194.96448,-137.74518 z M -537.90127,346.51705 c 1.46924,39.92741 12.29681,76.43626 30.82364,104.61667 21.05523,-28.18031 47.89674,-45.38011 75.18842,-53.68467 l -42.51189,-20.42423 c 33.35441,-61.94281 101.60759,-105.32857 167.51673,-101.23635 42.4233,1.43194 82.33203,22.18879 112.01381,52.33897 21.0994,21.43235 53.37417,31.44124 79.38393,16.13305 l 10.39581,-6.11852 c -43.49739,-68.86468 -120.54264,-113.8095 -201.79355,-112.04872 -87.97208,4.41046 -162.60049,62.73965 -194.96429,137.74477 z" transform="matrix(0,-1.3167476,1.3167476,0,-94.029776,21.910887)"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
6
internal/sync/static/popper.min-2.9.2.js
Normal file
6
internal/sync/static/popper.min-2.9.2.js
Normal file
File diff suppressed because one or more lines are too long
80
internal/sync/static/static.go
Normal file
80
internal/sync/static/static.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package static
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//go:embed index.html
|
||||||
|
index string
|
||||||
|
|
||||||
|
//go:embed favicon.ico
|
||||||
|
favicon []byte
|
||||||
|
|
||||||
|
//go:embed logo.svg
|
||||||
|
logo []byte
|
||||||
|
|
||||||
|
//go:embed bootstrap.min-5.3.3.js
|
||||||
|
bootstrapJS []byte
|
||||||
|
|
||||||
|
// https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css
|
||||||
|
//go:embed bootstrap.min-5.3.3.css
|
||||||
|
bootstrapCSS []byte
|
||||||
|
|
||||||
|
// https://bootswatch.com/5/darkly/bootstrap.min.css
|
||||||
|
//go:embed bootstrap.min-darkly-5.3.css
|
||||||
|
bootstrapDarkCSS []byte
|
||||||
|
|
||||||
|
// https://code.jquery.com/jquery-3.7.1.min.js
|
||||||
|
//go:embed jquery-3.7.1.min.js
|
||||||
|
jquery []byte
|
||||||
|
|
||||||
|
// https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js
|
||||||
|
//go:embed popper.min-2.9.2.js
|
||||||
|
popper []byte
|
||||||
|
|
||||||
|
// https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js
|
||||||
|
//go:embed chart.umd.min-4.4.7.js
|
||||||
|
chart []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleFavicon(c *gin.Context) {
|
||||||
|
c.Data(http.StatusOK, "image/x-icon", favicon)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleLogo(c *gin.Context) {
|
||||||
|
c.Data(http.StatusOK, "image/svg+xml", logo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Index() string {
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleResources(group gin.IRouter, dark bool) {
|
||||||
|
group.GET("/favicon.ico", handleFavicon)
|
||||||
|
group.GET("/logo.svg", handleLogo)
|
||||||
|
group.GET("/lib/jquery.js", handleJS(jquery))
|
||||||
|
group.GET("/lib/popper.js", handleJS(popper))
|
||||||
|
group.GET("/lib/chart.js", handleJS(chart))
|
||||||
|
group.GET("/lib/bootstrap.js", handleJS(bootstrapJS))
|
||||||
|
if dark {
|
||||||
|
group.GET("/lib/bootstrap.css", handleCSS(bootstrapDarkCSS))
|
||||||
|
} else {
|
||||||
|
group.GET("/lib/bootstrap.css", handleCSS(bootstrapCSS))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleJS(bytes []byte) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
c.Data(http.StatusOK, "application/javascript", bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleCSS(bytes []byte) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
c.Data(http.StatusOK, "text/css", bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
381
internal/sync/sync.go
Normal file
381
internal/sync/sync.go
Normal file
@@ -0,0 +1,381 @@
|
|||||||
|
package sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/robfig/cron/v3"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/log"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/metrics"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/utils"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/versions"
|
||||||
|
"github.com/bakito/adguardhome-sync/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
var l = log.GetLogger("sync")
|
||||||
|
|
||||||
|
// Sync config from origin to replica.
|
||||||
|
func Sync(cfg *types.Config) error {
|
||||||
|
if cfg.Origin.URL == "" {
|
||||||
|
return errors.New("origin URL is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.UniqueReplicas()) == 0 {
|
||||||
|
return errors.New("no replicas configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
l.With(
|
||||||
|
"version", version.Version,
|
||||||
|
"build", version.Build,
|
||||||
|
"os", runtime.GOOS,
|
||||||
|
"arch", runtime.GOARCH,
|
||||||
|
).Info("AdGuardHome sync")
|
||||||
|
cfg.Log(l)
|
||||||
|
cfg.Features.LogDisabled(l)
|
||||||
|
cfg.Origin.AutoSetup = false
|
||||||
|
|
||||||
|
w := &worker{
|
||||||
|
cfg: cfg,
|
||||||
|
createClient: client.New,
|
||||||
|
}
|
||||||
|
if cfg.Cron != "" {
|
||||||
|
w.cron = cron.New()
|
||||||
|
cl := l.With("cron", cfg.Cron)
|
||||||
|
sched, err := cron.ParseStandard(cfg.Cron)
|
||||||
|
if err != nil {
|
||||||
|
cl.With("error", err).Error("Error parsing cron expression")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cl = cl.With("next-execution", sched.Next(time.Now()))
|
||||||
|
_, err = w.cron.AddFunc(cfg.Cron, func() {
|
||||||
|
w.sync()
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
cl.With("error", err).Error("Error during cron job setup")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cl.Info("Setup cronjob")
|
||||||
|
if cfg.API.Port != 0 {
|
||||||
|
w.cron.Start()
|
||||||
|
} else {
|
||||||
|
runOnStartAsync(cfg, w)
|
||||||
|
w.cron.Run()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cfg.API.Port != 0 {
|
||||||
|
runOnStartAsync(cfg, w)
|
||||||
|
w.listenAndServe()
|
||||||
|
} else if cfg.RunOnStart {
|
||||||
|
l.Info("Running sync on startup")
|
||||||
|
w.sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runOnStartAsync(cfg *types.Config, w *worker) {
|
||||||
|
if cfg.RunOnStart {
|
||||||
|
go func() {
|
||||||
|
l.Info("Running sync on startup")
|
||||||
|
w.sync()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type worker struct {
|
||||||
|
cfg *types.Config
|
||||||
|
running bool
|
||||||
|
cron *cron.Cron
|
||||||
|
createClient func(instance types.AdGuardInstance) (client.Client, error)
|
||||||
|
actions []syncAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *worker) status() *syncStatus {
|
||||||
|
syncStatus := &syncStatus{
|
||||||
|
Origin: w.getStatus(*w.cfg.Origin),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, replica := range w.cfg.Replicas {
|
||||||
|
st := w.getStatus(replica)
|
||||||
|
if w.running {
|
||||||
|
st.Status = "info"
|
||||||
|
}
|
||||||
|
syncStatus.Replicas = append(syncStatus.Replicas, st)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(syncStatus.Replicas, func(i, j int) bool {
|
||||||
|
return syncStatus.Replicas[i].Host < syncStatus.Replicas[j].Host
|
||||||
|
})
|
||||||
|
|
||||||
|
syncStatus.SyncRunning = w.running
|
||||||
|
|
||||||
|
return syncStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *worker) getStatus(inst types.AdGuardInstance) replicaStatus {
|
||||||
|
st := replicaStatus{Host: inst.WebHost, URL: inst.WebURL}
|
||||||
|
|
||||||
|
oc, err := w.createClient(inst)
|
||||||
|
if err != nil {
|
||||||
|
l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client")
|
||||||
|
st.Status = "danger"
|
||||||
|
st.Error = err.Error()
|
||||||
|
return st
|
||||||
|
}
|
||||||
|
sl := l.With("from", inst.WebHost)
|
||||||
|
status, err := oc.Status()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, client.ErrSetupNeeded) {
|
||||||
|
st.Status = "warning"
|
||||||
|
st.Error = err.Error()
|
||||||
|
return st
|
||||||
|
}
|
||||||
|
sl.With("error", err).Error("Error getting origin status")
|
||||||
|
st.Status = "danger"
|
||||||
|
st.Error = err.Error()
|
||||||
|
return st
|
||||||
|
}
|
||||||
|
st.Status = "success"
|
||||||
|
st.ProtectionEnabled = utils.Ptr(status.ProtectionEnabled)
|
||||||
|
return st
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *worker) sync() {
|
||||||
|
if w.running {
|
||||||
|
l.Info("Sync already running")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.running = true
|
||||||
|
defer func() {
|
||||||
|
w.running = false
|
||||||
|
}()
|
||||||
|
|
||||||
|
oc, err := w.createClient(*w.cfg.Origin)
|
||||||
|
if err != nil {
|
||||||
|
l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sl := l.With("from", oc.Host())
|
||||||
|
|
||||||
|
o := &origin{}
|
||||||
|
o.status, err = oc.Status()
|
||||||
|
if err != nil {
|
||||||
|
sl.With("error", err).Error("Error getting origin status")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if versions.IsNewerThan(versions.MinAgh, o.status.Version) {
|
||||||
|
sl.With("error", err, "version", o.status.Version).
|
||||||
|
Errorf("Origin AdGuard Home version must be >= %s", versions.MinAgh)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sl.With("version", o.status.Version).Info("Connected to origin")
|
||||||
|
|
||||||
|
o.profileInfo, err = oc.ProfileInfo()
|
||||||
|
if err != nil {
|
||||||
|
sl.With("error", err).Error("Error getting profileInfo info")
|
||||||
|
|
||||||
|
// Workaround for https://github.com/AdguardTeam/AdGuardHome/issues/7987
|
||||||
|
// and https://github.com/AdguardTeam/AdGuardHome/issues/7985
|
||||||
|
|
||||||
|
clientErr := &client.Error{}
|
||||||
|
if !w.cfg.ContinueOnError || !errors.As(err, &clientErr) || clientErr.Code() != http.StatusUnauthorized {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
o.parental, err = oc.Parental()
|
||||||
|
if err != nil {
|
||||||
|
sl.With("error", err).Error("Error getting parental status")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
o.safeSearch, err = oc.SafeSearchConfig()
|
||||||
|
if err != nil {
|
||||||
|
sl.With("error", err).Error("Error getting safe search status")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
o.safeBrowsing, err = oc.SafeBrowsing()
|
||||||
|
if err != nil {
|
||||||
|
sl.With("error", err).Error("Error getting safe browsing status")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
o.rewrites, err = oc.RewriteList()
|
||||||
|
if err != nil {
|
||||||
|
sl.With("error", err).Error("Error getting origin rewrites")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
o.blockedServicesSchedule, err = oc.BlockedServicesSchedule()
|
||||||
|
if err != nil {
|
||||||
|
sl.With("error", err).Error("Error getting origin blocked services schedule")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
o.filters, err = oc.Filtering()
|
||||||
|
if err != nil {
|
||||||
|
sl.With("error", err).Error("Error getting origin actionFilters")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
o.clients, err = oc.Clients()
|
||||||
|
if err != nil {
|
||||||
|
sl.With("error", err).Error("Error getting origin clients")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
o.queryLogConfig, err = oc.QueryLogConfig()
|
||||||
|
if err != nil {
|
||||||
|
sl.With("error", err).Error("Error getting query log config")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
o.statsConfig, err = oc.StatsConfig()
|
||||||
|
if err != nil {
|
||||||
|
sl.With("error", err).Error("Error getting stats config")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
o.accessList, err = oc.AccessList()
|
||||||
|
if err != nil {
|
||||||
|
sl.With("error", err).Error("Error getting access list")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
o.dnsConfig, err = oc.DNSConfig()
|
||||||
|
if err != nil {
|
||||||
|
sl.With("error", err).Error("Error getting dns config")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.cfg.Features.DHCP.ServerConfig || w.cfg.Features.DHCP.StaticLeases {
|
||||||
|
o.dhcpServerConfig, err = oc.DhcpConfig()
|
||||||
|
if err != nil {
|
||||||
|
sl.With("error", err).Error("Error getting dhcp server config")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.cfg.Features.TLSConfig {
|
||||||
|
o.tlsConfig, err = oc.TLSConfig()
|
||||||
|
if err != nil {
|
||||||
|
sl.With("error", err).Error("Error getting tls config")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.actions = setupActions(w.cfg)
|
||||||
|
|
||||||
|
replicas := w.cfg.UniqueReplicas()
|
||||||
|
for _, replica := range replicas {
|
||||||
|
w.syncTo(sl, o, replica)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *worker) syncTo(l *zap.SugaredLogger, o *origin, replica types.AdGuardInstance) {
|
||||||
|
rc, err := w.createClient(replica)
|
||||||
|
if err != nil {
|
||||||
|
l.With("error", err, "url", replica.URL).Error("Error creating replica client")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rl := l.With("to", rc.Host())
|
||||||
|
rl.Info("Start sync")
|
||||||
|
start := time.Now()
|
||||||
|
withError := false
|
||||||
|
delta := time.Since(start).Seconds()
|
||||||
|
defer func() {
|
||||||
|
metrics.UpdateResult(rc.Host(), !withError, delta)
|
||||||
|
doneLog := rl.With("duration", fmt.Sprintf("%vs", delta))
|
||||||
|
if withError {
|
||||||
|
doneLog.Error("Sync done")
|
||||||
|
} else {
|
||||||
|
doneLog.Info("Sync done")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
replicaStatus, err := w.statusWithSetup(rl, replica, rc)
|
||||||
|
if err != nil {
|
||||||
|
rl.With("error", err).Error("Error getting replica status")
|
||||||
|
withError = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rl.With("version", replicaStatus.Version).Info("Connected to replica")
|
||||||
|
|
||||||
|
if versions.IsNewerThan(versions.MinAgh, replicaStatus.Version) {
|
||||||
|
rl.With("error", err, "version", replicaStatus.Version).
|
||||||
|
Errorf("Replica AdGuard Home version must be >= %s", versions.MinAgh)
|
||||||
|
withError = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.status.Version != replicaStatus.Version {
|
||||||
|
rl.With("originVersion", o.status.Version, "replicaVersion", replicaStatus.Version).
|
||||||
|
Warn("Versions do not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
ac := &actionContext{
|
||||||
|
cfg: w.cfg,
|
||||||
|
rl: rl,
|
||||||
|
origin: o,
|
||||||
|
replicaStatus: replicaStatus,
|
||||||
|
client: rc,
|
||||||
|
replica: replica,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, action := range w.actions {
|
||||||
|
if err := action.sync(ac); err != nil {
|
||||||
|
rl.With("error", err).Errorf("Error syncing %s", action.name())
|
||||||
|
withError = true
|
||||||
|
if !w.cfg.ContinueOnError {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *worker) statusWithSetup(
|
||||||
|
rl *zap.SugaredLogger,
|
||||||
|
replica types.AdGuardInstance,
|
||||||
|
rc client.Client,
|
||||||
|
) (*model.ServerStatus, error) {
|
||||||
|
rs, err := rc.Status()
|
||||||
|
if err != nil {
|
||||||
|
if replica.AutoSetup && errors.Is(err, client.ErrSetupNeeded) {
|
||||||
|
if serr := rc.Setup(); serr != nil {
|
||||||
|
rl.With("error", serr).Error("Error setup AdGuardHome")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return rc.Status()
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return rs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type origin struct {
|
||||||
|
status *model.ServerStatus
|
||||||
|
rewrites *model.RewriteEntries
|
||||||
|
blockedServicesSchedule *model.BlockedServicesSchedule
|
||||||
|
filters *model.FilterStatus
|
||||||
|
clients *model.Clients
|
||||||
|
queryLogConfig *model.QueryLogConfigWithIgnored
|
||||||
|
statsConfig *model.GetStatsConfigResponse
|
||||||
|
accessList *model.AccessList
|
||||||
|
dnsConfig *model.DNSConfig
|
||||||
|
dhcpServerConfig *model.DhcpStatus
|
||||||
|
parental bool
|
||||||
|
safeSearch *model.SafeSearchConfig
|
||||||
|
profileInfo *model.ProfileInfo
|
||||||
|
safeBrowsing bool
|
||||||
|
tlsConfig *model.TlsConfig
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ package sync_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
||||||
713
internal/sync/sync_test.go
Normal file
713
internal/sync/sync_test.go
Normal file
@@ -0,0 +1,713 @@
|
|||||||
|
package sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
gm "go.uber.org/mock/gomock"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/client/model"
|
||||||
|
clientmock "github.com/bakito/adguardhome-sync/internal/mocks/client"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/types"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/utils"
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/versions"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Sync", func() {
|
||||||
|
var (
|
||||||
|
mockCtrl *gm.Controller
|
||||||
|
cl *clientmock.MockClient
|
||||||
|
w *worker
|
||||||
|
te error
|
||||||
|
ac *actionContext
|
||||||
|
)
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
mockCtrl = gm.NewController(GinkgoT())
|
||||||
|
cl = clientmock.NewMockClient(mockCtrl)
|
||||||
|
w = &worker{
|
||||||
|
createClient: func(instance types.AdGuardInstance) (client.Client, error) {
|
||||||
|
return cl, nil
|
||||||
|
},
|
||||||
|
cfg: &types.Config{
|
||||||
|
Features: types.Features{
|
||||||
|
DHCP: types.DHCP{
|
||||||
|
ServerConfig: true,
|
||||||
|
StaticLeases: true,
|
||||||
|
},
|
||||||
|
DNS: types.DNS{
|
||||||
|
ServerConfig: true,
|
||||||
|
Rewrites: true,
|
||||||
|
AccessLists: true,
|
||||||
|
},
|
||||||
|
Filters: true,
|
||||||
|
ClientSettings: true,
|
||||||
|
Services: true,
|
||||||
|
GeneralSettings: true,
|
||||||
|
StatsConfig: true,
|
||||||
|
QueryLogConfig: true,
|
||||||
|
Theme: true,
|
||||||
|
},
|
||||||
|
Replicas: []types.AdGuardInstance{
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
te = errors.New(uuid.NewString())
|
||||||
|
|
||||||
|
ac = &actionContext{
|
||||||
|
cfg: w.cfg,
|
||||||
|
rl: l,
|
||||||
|
origin: &origin{
|
||||||
|
profileInfo: &model.ProfileInfo{
|
||||||
|
Name: "origin",
|
||||||
|
Language: "en",
|
||||||
|
Theme: "auto",
|
||||||
|
},
|
||||||
|
status: &model.ServerStatus{},
|
||||||
|
safeSearch: &model.SafeSearchConfig{},
|
||||||
|
queryLogConfig: &model.QueryLogConfigWithIgnored{},
|
||||||
|
statsConfig: &model.PutStatsConfigUpdateRequest{},
|
||||||
|
},
|
||||||
|
replicaStatus: &model.ServerStatus{},
|
||||||
|
client: cl,
|
||||||
|
replica: w.cfg.Replicas[0],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
AfterEach(func() {
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("worker", func() {
|
||||||
|
Context("actionDNSRewrites", func() {
|
||||||
|
var (
|
||||||
|
domain string
|
||||||
|
answer string
|
||||||
|
reO model.RewriteEntries
|
||||||
|
reR model.RewriteEntries
|
||||||
|
)
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
domain = uuid.NewString()
|
||||||
|
answer = uuid.NewString()
|
||||||
|
reO = model.RewriteEntries{{Domain: utils.Ptr(domain), Answer: utils.Ptr(answer)}}
|
||||||
|
reR = model.RewriteEntries{{Domain: utils.Ptr(domain), Answer: utils.Ptr(answer)}}
|
||||||
|
})
|
||||||
|
It("should have no changes (empty slices)", func() {
|
||||||
|
ac.origin.rewrites = &reO
|
||||||
|
cl.EXPECT().RewriteList().Return(&reR, nil)
|
||||||
|
cl.EXPECT().AddRewriteEntries()
|
||||||
|
cl.EXPECT().DeleteRewriteEntries()
|
||||||
|
err := actionDNSRewrites(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should add one rewrite entry", func() {
|
||||||
|
reR = []model.RewriteEntry{}
|
||||||
|
ac.origin.rewrites = &reO
|
||||||
|
cl.EXPECT().RewriteList().Return(&reR, nil)
|
||||||
|
cl.EXPECT().AddRewriteEntries(reO[0])
|
||||||
|
cl.EXPECT().DeleteRewriteEntries()
|
||||||
|
err := actionDNSRewrites(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should remove one rewrite entry", func() {
|
||||||
|
reO = []model.RewriteEntry{}
|
||||||
|
ac.origin.rewrites = &reO
|
||||||
|
cl.EXPECT().RewriteList().Return(&reR, nil)
|
||||||
|
cl.EXPECT().AddRewriteEntries()
|
||||||
|
cl.EXPECT().DeleteRewriteEntries(reR[0])
|
||||||
|
err := actionDNSRewrites(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should remove one rewrite entry", func() {
|
||||||
|
reO = []model.RewriteEntry{}
|
||||||
|
ac.origin.rewrites = &reO
|
||||||
|
cl.EXPECT().RewriteList().Return(&reR, nil)
|
||||||
|
cl.EXPECT().AddRewriteEntries()
|
||||||
|
cl.EXPECT().DeleteRewriteEntries(reR[0])
|
||||||
|
err := actionDNSRewrites(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should return error when error on RewriteList()", func() {
|
||||||
|
ac.origin.rewrites = &reO
|
||||||
|
cl.EXPECT().RewriteList().Return(nil, te)
|
||||||
|
err := actionDNSRewrites(ac)
|
||||||
|
Ω(err).Should(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should return error when error on AddRewriteEntries()", func() {
|
||||||
|
ac.origin.rewrites = &reO
|
||||||
|
cl.EXPECT().RewriteList().Return(&reR, nil)
|
||||||
|
cl.EXPECT().DeleteRewriteEntries()
|
||||||
|
cl.EXPECT().AddRewriteEntries().Return(te)
|
||||||
|
err := actionDNSRewrites(ac)
|
||||||
|
Ω(err).Should(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should return error when error on DeleteRewriteEntries()", func() {
|
||||||
|
ac.origin.rewrites = &reO
|
||||||
|
cl.EXPECT().RewriteList().Return(&reR, nil)
|
||||||
|
cl.EXPECT().DeleteRewriteEntries().Return(te)
|
||||||
|
err := actionDNSRewrites(ac)
|
||||||
|
Ω(err).Should(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("actionClientSettings", func() {
|
||||||
|
var (
|
||||||
|
clR *model.Clients
|
||||||
|
name string
|
||||||
|
)
|
||||||
|
BeforeEach(func() {
|
||||||
|
name = uuid.NewString()
|
||||||
|
ac.origin.clients = &model.Clients{Clients: &model.ClientsArray{{Name: utils.Ptr(name)}}}
|
||||||
|
clR = &model.Clients{Clients: &model.ClientsArray{{Name: utils.Ptr(name)}}}
|
||||||
|
})
|
||||||
|
It("should have no changes (empty slices)", func() {
|
||||||
|
cl.EXPECT().Clients().Return(clR, nil)
|
||||||
|
err := actionClientSettings(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should add one client", func() {
|
||||||
|
clR.Clients = &model.ClientsArray{}
|
||||||
|
cl.EXPECT().Clients().Return(clR, nil)
|
||||||
|
cl.EXPECT().AddClient(&(*ac.origin.clients.Clients)[0])
|
||||||
|
err := actionClientSettings(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should update one client", func() {
|
||||||
|
(*clR.Clients)[0].FilteringEnabled = utils.Ptr(true)
|
||||||
|
cl.EXPECT().Clients().Return(clR, nil)
|
||||||
|
cl.EXPECT().UpdateClient(&(*ac.origin.clients.Clients)[0])
|
||||||
|
err := actionClientSettings(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should delete one client", func() {
|
||||||
|
ac.origin.clients.Clients = &model.ClientsArray{}
|
||||||
|
cl.EXPECT().Clients().Return(clR, nil)
|
||||||
|
cl.EXPECT().DeleteClient(&(*clR.Clients)[0])
|
||||||
|
err := actionClientSettings(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should return error when error on Clients()", func() {
|
||||||
|
cl.EXPECT().Clients().Return(nil, te)
|
||||||
|
err := actionClientSettings(ac)
|
||||||
|
Ω(err).Should(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("actionParental", func() {
|
||||||
|
It("should have no changes", func() {
|
||||||
|
cl.EXPECT().Parental()
|
||||||
|
err := actionParental(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should have parental enabled changes", func() {
|
||||||
|
ac.origin.parental = true
|
||||||
|
cl.EXPECT().Parental()
|
||||||
|
cl.EXPECT().ToggleParental(true)
|
||||||
|
err := actionParental(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("actionProtection", func() {
|
||||||
|
It("should have no changes", func() {
|
||||||
|
err := actionProtection(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should have protection enabled changes", func() {
|
||||||
|
ac.origin.status.ProtectionEnabled = true
|
||||||
|
cl.EXPECT().ToggleProtection(true)
|
||||||
|
err := actionProtection(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("actionSafeSearchConfig", func() {
|
||||||
|
It("should have no changes", func() {
|
||||||
|
cl.EXPECT().SafeSearchConfig().Return(ac.origin.safeSearch, nil)
|
||||||
|
|
||||||
|
err := actionSafeSearchConfig(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should have safeSearch enabled changes", func() {
|
||||||
|
ac.origin.safeSearch = &model.SafeSearchConfig{Enabled: utils.Ptr(true)}
|
||||||
|
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
|
||||||
|
cl.EXPECT().SetSafeSearchConfig(ac.origin.safeSearch)
|
||||||
|
err := actionSafeSearchConfig(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should have Duckduckgo safeSearch enabled changed", func() {
|
||||||
|
ac.origin.safeSearch = &model.SafeSearchConfig{Duckduckgo: utils.Ptr(true)}
|
||||||
|
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{Google: utils.Ptr(true)}, nil)
|
||||||
|
cl.EXPECT().SetSafeSearchConfig(ac.origin.safeSearch)
|
||||||
|
err := actionSafeSearchConfig(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("actionProfileInfo", func() {
|
||||||
|
It("should have no changes", func() {
|
||||||
|
cl.EXPECT().ProfileInfo().Return(ac.origin.profileInfo, nil)
|
||||||
|
err := actionProfileInfo(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should have profileInfo language changed", func() {
|
||||||
|
ac.origin.profileInfo.Language = "de"
|
||||||
|
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{Name: "replica", Language: "en"}, nil)
|
||||||
|
cl.EXPECT().SetProfileInfo(&model.ProfileInfo{
|
||||||
|
Language: "de",
|
||||||
|
Name: "replica",
|
||||||
|
Theme: "auto",
|
||||||
|
})
|
||||||
|
err := actionProfileInfo(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should not change theme if feature is disabled", func() {
|
||||||
|
ac.origin.profileInfo.Language = "de"
|
||||||
|
ac.cfg.Features.Theme = false
|
||||||
|
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{Name: "replica", Language: "en"}, nil)
|
||||||
|
cl.EXPECT().SetProfileInfo(&model.ProfileInfo{
|
||||||
|
Language: "de",
|
||||||
|
Name: "replica",
|
||||||
|
Theme: "",
|
||||||
|
})
|
||||||
|
err := actionProfileInfo(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should not sync profileInfo if language is not set", func() {
|
||||||
|
ac.origin.profileInfo.Language = ""
|
||||||
|
cl.EXPECT().
|
||||||
|
ProfileInfo().
|
||||||
|
Return(&model.ProfileInfo{Name: "replica", Language: "en", Theme: "auto"}, nil)
|
||||||
|
cl.EXPECT().SetProfileInfo(ac.origin.profileInfo).Times(0)
|
||||||
|
err := actionProfileInfo(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should not sync profileInfo if theme is not set", func() {
|
||||||
|
ac.origin.profileInfo.Theme = ""
|
||||||
|
cl.EXPECT().
|
||||||
|
ProfileInfo().
|
||||||
|
Return(&model.ProfileInfo{Name: "replica", Language: "en", Theme: "auto"}, nil)
|
||||||
|
cl.EXPECT().SetProfileInfo(ac.origin.profileInfo).Times(0)
|
||||||
|
err := actionProfileInfo(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("actionSafeBrowsing", func() {
|
||||||
|
It("should have no changes", func() {
|
||||||
|
cl.EXPECT().SafeBrowsing()
|
||||||
|
err := actionSafeBrowsing(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should have safeBrowsing enabled changes", func() {
|
||||||
|
ac.origin.safeBrowsing = true
|
||||||
|
cl.EXPECT().SafeBrowsing()
|
||||||
|
cl.EXPECT().ToggleSafeBrowsing(true)
|
||||||
|
err := actionSafeBrowsing(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("actionQueryLogConfig", func() {
|
||||||
|
var qlc *model.QueryLogConfigWithIgnored
|
||||||
|
BeforeEach(func() {
|
||||||
|
qlc = &model.QueryLogConfigWithIgnored{}
|
||||||
|
})
|
||||||
|
It("should have no changes", func() {
|
||||||
|
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
|
||||||
|
err := actionQueryLogConfig(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should have QueryLogConfig changes", func() {
|
||||||
|
var interval model.QueryLogConfigInterval = 123
|
||||||
|
ac.origin.queryLogConfig.Interval = &interval
|
||||||
|
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
|
||||||
|
cl.EXPECT().
|
||||||
|
SetQueryLogConfig(&model.QueryLogConfigWithIgnored{QueryLogConfig: model.QueryLogConfig{AnonymizeClientIp: nil, Interval: &interval, Enabled: nil}})
|
||||||
|
err := actionQueryLogConfig(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("syncConfigs", func() {
|
||||||
|
var sc *model.PutStatsConfigUpdateRequest
|
||||||
|
BeforeEach(func() {
|
||||||
|
sc = &model.PutStatsConfigUpdateRequest{}
|
||||||
|
})
|
||||||
|
It("should have no changes", func() {
|
||||||
|
cl.EXPECT().StatsConfig().Return(sc, nil)
|
||||||
|
err := actionStatsConfig(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should have StatsConfig changes", func() {
|
||||||
|
var interval float32 = 123
|
||||||
|
ac.origin.statsConfig.Interval = interval
|
||||||
|
cl.EXPECT().StatsConfig().Return(sc, nil)
|
||||||
|
cl.EXPECT().SetStatsConfig(&model.PutStatsConfigUpdateRequest{Interval: interval})
|
||||||
|
err := actionStatsConfig(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("statusWithSetup", func() {
|
||||||
|
var (
|
||||||
|
status *model.ServerStatus
|
||||||
|
inst types.AdGuardInstance
|
||||||
|
)
|
||||||
|
BeforeEach(func() {
|
||||||
|
status = &model.ServerStatus{}
|
||||||
|
inst = types.AdGuardInstance{
|
||||||
|
AutoSetup: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
It("should get the replica status", func() {
|
||||||
|
cl.EXPECT().Status().Return(status, nil)
|
||||||
|
st, err := w.statusWithSetup(l, inst, cl)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(st).Should(Equal(status))
|
||||||
|
})
|
||||||
|
It("should runs setup before getting replica status", func() {
|
||||||
|
cl.EXPECT().Status().Return(nil, client.ErrSetupNeeded)
|
||||||
|
cl.EXPECT().Setup()
|
||||||
|
cl.EXPECT().Status().Return(status, nil)
|
||||||
|
st, err := w.statusWithSetup(l, inst, cl)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(st).Should(Equal(status))
|
||||||
|
})
|
||||||
|
It("should fail on setup", func() {
|
||||||
|
cl.EXPECT().Status().Return(nil, client.ErrSetupNeeded)
|
||||||
|
cl.EXPECT().Setup().Return(te)
|
||||||
|
st, err := w.statusWithSetup(l, inst, cl)
|
||||||
|
Ω(err).Should(HaveOccurred())
|
||||||
|
Ω(st).Should(BeNil())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("actionBlockedServicesSchedule", func() {
|
||||||
|
var rbss *model.BlockedServicesSchedule
|
||||||
|
BeforeEach(func() {
|
||||||
|
ac.origin.blockedServicesSchedule = &model.BlockedServicesSchedule{}
|
||||||
|
rbss = &model.BlockedServicesSchedule{}
|
||||||
|
})
|
||||||
|
It("should have no changes", func() {
|
||||||
|
cl.EXPECT().BlockedServicesSchedule().Return(ac.origin.blockedServicesSchedule, nil)
|
||||||
|
err := actionBlockedServicesSchedule(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should have blockedServices schedule changes", func() {
|
||||||
|
ac.origin.blockedServicesSchedule = &model.BlockedServicesSchedule{Ids: utils.Ptr([]string{"bar"})}
|
||||||
|
|
||||||
|
cl.EXPECT().BlockedServicesSchedule().Return(rbss, nil)
|
||||||
|
cl.EXPECT().SetBlockedServicesSchedule(ac.origin.blockedServicesSchedule)
|
||||||
|
err := actionBlockedServicesSchedule(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("syncFilters", func() {
|
||||||
|
var rf *model.FilterStatus
|
||||||
|
BeforeEach(func() {
|
||||||
|
ac.origin.filters = &model.FilterStatus{}
|
||||||
|
rf = &model.FilterStatus{}
|
||||||
|
})
|
||||||
|
It("should have no changes", func() {
|
||||||
|
cl.EXPECT().Filtering().Return(rf, nil)
|
||||||
|
err := actionFilters(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should have changes user roles", func() {
|
||||||
|
ac.origin.filters.UserRules = utils.Ptr([]string{"foo"})
|
||||||
|
cl.EXPECT().Filtering().Return(rf, nil)
|
||||||
|
cl.EXPECT().SetCustomRules(ac.origin.filters.UserRules)
|
||||||
|
err := actionFilters(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should have changed filtering config", func() {
|
||||||
|
ac.origin.filters.Enabled = utils.Ptr(true)
|
||||||
|
ac.origin.filters.Interval = utils.Ptr(123)
|
||||||
|
cl.EXPECT().Filtering().Return(rf, nil)
|
||||||
|
cl.EXPECT().ToggleFiltering(*ac.origin.filters.Enabled, *ac.origin.filters.Interval)
|
||||||
|
err := actionFilters(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should add a filter", func() {
|
||||||
|
ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
|
||||||
|
cl.EXPECT().Filtering().Return(rf, nil)
|
||||||
|
cl.EXPECT().AddFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"})
|
||||||
|
cl.EXPECT().RefreshFilters(gm.Any())
|
||||||
|
err := actionFilters(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should delete a filter", func() {
|
||||||
|
rf.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
|
||||||
|
cl.EXPECT().Filtering().Return(rf, nil)
|
||||||
|
cl.EXPECT().DeleteFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"})
|
||||||
|
err := actionFilters(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should update a filter", func() {
|
||||||
|
ac.origin.filters.Filters = utils.Ptr(
|
||||||
|
[]model.Filter{{Name: "foo", Url: "https://foo.bar", Enabled: true}},
|
||||||
|
)
|
||||||
|
rf.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
|
||||||
|
cl.EXPECT().Filtering().Return(rf, nil)
|
||||||
|
cl.EXPECT().UpdateFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar", Enabled: true})
|
||||||
|
cl.EXPECT().RefreshFilters(gm.Any())
|
||||||
|
err := actionFilters(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should abort after failed added filter", func() {
|
||||||
|
ac.cfg.ContinueOnError = false
|
||||||
|
ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
|
||||||
|
cl.EXPECT().Filtering().Return(rf, nil)
|
||||||
|
cl.EXPECT().
|
||||||
|
AddFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"}).
|
||||||
|
Return(errors.New("test failure"))
|
||||||
|
err := actionFilters(ac)
|
||||||
|
Ω(err).Should(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should continue after failed added filter", func() {
|
||||||
|
ac.cfg.ContinueOnError = true
|
||||||
|
ac.origin.filters.Filters = utils.Ptr(
|
||||||
|
[]model.Filter{{Name: "foo", Url: "https://foo.bar"}, {Name: "bar", Url: "https://bar.foo"}},
|
||||||
|
)
|
||||||
|
cl.EXPECT().Filtering().Return(rf, nil)
|
||||||
|
cl.EXPECT().
|
||||||
|
AddFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"}).
|
||||||
|
Return(errors.New("test failure"))
|
||||||
|
cl.EXPECT().AddFilter(false, model.Filter{Name: "bar", Url: "https://bar.foo"})
|
||||||
|
cl.EXPECT().RefreshFilters(gm.Any())
|
||||||
|
err := actionFilters(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("actionDNSAccessLists", func() {
|
||||||
|
var ral *model.AccessList
|
||||||
|
BeforeEach(func() {
|
||||||
|
ac.origin.accessList = &model.AccessList{}
|
||||||
|
ral = &model.AccessList{}
|
||||||
|
})
|
||||||
|
It("should have no changes", func() {
|
||||||
|
cl.EXPECT().AccessList().Return(ral, nil)
|
||||||
|
err := actionDNSAccessLists(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should have access list changes", func() {
|
||||||
|
ral.BlockedHosts = utils.Ptr([]string{"foo"})
|
||||||
|
cl.EXPECT().AccessList().Return(ral, nil)
|
||||||
|
cl.EXPECT().SetAccessList(ac.origin.accessList)
|
||||||
|
err := actionDNSAccessLists(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("actionDNSServerConfig", func() {
|
||||||
|
var rdc *model.DNSConfig
|
||||||
|
BeforeEach(func() {
|
||||||
|
ac.origin.dnsConfig = &model.DNSConfig{}
|
||||||
|
rdc = &model.DNSConfig{}
|
||||||
|
})
|
||||||
|
It("should have no changes", func() {
|
||||||
|
cl.EXPECT().DNSConfig().Return(rdc, nil)
|
||||||
|
err := actionDNSServerConfig(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should have dns config changes", func() {
|
||||||
|
rdc.BootstrapDns = utils.Ptr([]string{"foo"})
|
||||||
|
cl.EXPECT().DNSConfig().Return(rdc, nil)
|
||||||
|
cl.EXPECT().SetDNSConfig(ac.origin.dnsConfig)
|
||||||
|
err := actionDNSServerConfig(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("actionDHCPServerConfig", func() {
|
||||||
|
var rsc *model.DhcpStatus
|
||||||
|
BeforeEach(func() {
|
||||||
|
ac.origin.dhcpServerConfig = &model.DhcpStatus{
|
||||||
|
V4: &model.DhcpConfigV4{
|
||||||
|
GatewayIp: utils.Ptr("1.2.3.4"),
|
||||||
|
RangeStart: utils.Ptr("1.2.3.5"),
|
||||||
|
RangeEnd: utils.Ptr("1.2.3.6"),
|
||||||
|
SubnetMask: utils.Ptr("255.255.255.0"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
rsc = &model.DhcpStatus{}
|
||||||
|
w.cfg.Features.DHCP.StaticLeases = false
|
||||||
|
})
|
||||||
|
It("should have no changes", func() {
|
||||||
|
rsc.V4 = ac.origin.dhcpServerConfig.V4
|
||||||
|
cl.EXPECT().DhcpConfig().Return(rsc, nil)
|
||||||
|
err := actionDHCPServerConfig(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should have changes", func() {
|
||||||
|
rsc.Enabled = utils.Ptr(true)
|
||||||
|
cl.EXPECT().DhcpConfig().Return(rsc, nil)
|
||||||
|
cl.EXPECT().SetDhcpConfig(ac.origin.dhcpServerConfig)
|
||||||
|
err := actionDHCPServerConfig(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should use replica interface name", func() {
|
||||||
|
ac.replica.InterfaceName = "foo"
|
||||||
|
cl.EXPECT().DhcpConfig().Return(rsc, nil)
|
||||||
|
oscClone := ac.origin.dhcpServerConfig.Clone()
|
||||||
|
oscClone.InterfaceName = utils.Ptr("foo")
|
||||||
|
cl.EXPECT().SetDhcpConfig(oscClone)
|
||||||
|
err := actionDHCPServerConfig(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should enable the target dhcp server", func() {
|
||||||
|
ac.replica.DHCPServerEnabled = utils.Ptr(true)
|
||||||
|
cl.EXPECT().DhcpConfig().Return(rsc, nil)
|
||||||
|
oscClone := ac.origin.dhcpServerConfig.Clone()
|
||||||
|
oscClone.Enabled = utils.Ptr(true)
|
||||||
|
cl.EXPECT().SetDhcpConfig(oscClone)
|
||||||
|
err := actionDHCPServerConfig(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should not sync empty IPv4", func() {
|
||||||
|
ac.replica.DHCPServerEnabled = utils.Ptr(false)
|
||||||
|
ac.origin.dhcpServerConfig.V4 = &model.DhcpConfigV4{
|
||||||
|
GatewayIp: utils.Ptr(""),
|
||||||
|
}
|
||||||
|
err := actionDHCPServerConfig(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("sync", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
w.cfg = &types.Config{
|
||||||
|
Origin: &types.AdGuardInstance{},
|
||||||
|
Replica: &types.AdGuardInstance{URL: "foo"},
|
||||||
|
Features: types.Features{
|
||||||
|
DHCP: types.DHCP{
|
||||||
|
ServerConfig: true,
|
||||||
|
StaticLeases: true,
|
||||||
|
},
|
||||||
|
DNS: types.DNS{
|
||||||
|
ServerConfig: true,
|
||||||
|
Rewrites: true,
|
||||||
|
AccessLists: true,
|
||||||
|
},
|
||||||
|
Filters: true,
|
||||||
|
ClientSettings: true,
|
||||||
|
Services: true,
|
||||||
|
GeneralSettings: true,
|
||||||
|
StatsConfig: true,
|
||||||
|
QueryLogConfig: true,
|
||||||
|
TLSConfig: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
It("should have no changes", func() {
|
||||||
|
// origin
|
||||||
|
cl.EXPECT().Host().Times(2)
|
||||||
|
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
|
||||||
|
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
|
||||||
|
cl.EXPECT().Parental()
|
||||||
|
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
|
||||||
|
cl.EXPECT().SafeBrowsing()
|
||||||
|
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
|
||||||
|
cl.EXPECT().BlockedServicesSchedule()
|
||||||
|
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
|
||||||
|
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
|
||||||
|
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfigWithIgnored{}, nil)
|
||||||
|
cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
|
||||||
|
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
|
||||||
|
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
|
||||||
|
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)
|
||||||
|
cl.EXPECT().TLSConfig().Return(&model.TlsConfig{}, nil)
|
||||||
|
|
||||||
|
// replica
|
||||||
|
cl.EXPECT().Host()
|
||||||
|
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
|
||||||
|
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
|
||||||
|
cl.EXPECT().Parental()
|
||||||
|
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
|
||||||
|
cl.EXPECT().SafeBrowsing()
|
||||||
|
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfigWithIgnored{}, nil)
|
||||||
|
cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
|
||||||
|
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
|
||||||
|
cl.EXPECT().AddRewriteEntries()
|
||||||
|
cl.EXPECT().DeleteRewriteEntries()
|
||||||
|
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
|
||||||
|
cl.EXPECT().BlockedServicesSchedule()
|
||||||
|
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
|
||||||
|
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
|
||||||
|
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
|
||||||
|
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)
|
||||||
|
cl.EXPECT().TLSConfig().Return(&model.TlsConfig{}, nil)
|
||||||
|
w.sync()
|
||||||
|
})
|
||||||
|
It("should not sync DHCP", func() {
|
||||||
|
w.cfg.Features.DHCP.ServerConfig = false
|
||||||
|
w.cfg.Features.DHCP.StaticLeases = false
|
||||||
|
// origin
|
||||||
|
cl.EXPECT().Host().Times(2)
|
||||||
|
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
|
||||||
|
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
|
||||||
|
cl.EXPECT().Parental()
|
||||||
|
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
|
||||||
|
cl.EXPECT().SafeBrowsing()
|
||||||
|
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
|
||||||
|
cl.EXPECT().BlockedServicesSchedule()
|
||||||
|
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
|
||||||
|
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
|
||||||
|
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfigWithIgnored{}, nil)
|
||||||
|
cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
|
||||||
|
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
|
||||||
|
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
|
||||||
|
cl.EXPECT().TLSConfig().Return(&model.TlsConfig{}, nil)
|
||||||
|
|
||||||
|
// replica
|
||||||
|
cl.EXPECT().Host()
|
||||||
|
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
|
||||||
|
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
|
||||||
|
cl.EXPECT().Parental()
|
||||||
|
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
|
||||||
|
cl.EXPECT().SafeBrowsing()
|
||||||
|
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfigWithIgnored{}, nil)
|
||||||
|
cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
|
||||||
|
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
|
||||||
|
cl.EXPECT().AddRewriteEntries()
|
||||||
|
cl.EXPECT().DeleteRewriteEntries()
|
||||||
|
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
|
||||||
|
cl.EXPECT().BlockedServicesSchedule()
|
||||||
|
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
|
||||||
|
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
|
||||||
|
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
|
||||||
|
cl.EXPECT().TLSConfig().Return(&model.TlsConfig{}, nil)
|
||||||
|
w.sync()
|
||||||
|
})
|
||||||
|
It("origin version is too small", func() {
|
||||||
|
// origin
|
||||||
|
cl.EXPECT().Host()
|
||||||
|
cl.EXPECT().Status().Return(&model.ServerStatus{Version: "v0.106.9"}, nil)
|
||||||
|
w.sync()
|
||||||
|
})
|
||||||
|
It("replica version is too small", func() {
|
||||||
|
// origin
|
||||||
|
cl.EXPECT().Host()
|
||||||
|
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
|
||||||
|
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
|
||||||
|
cl.EXPECT().Parental()
|
||||||
|
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
|
||||||
|
cl.EXPECT().SafeBrowsing()
|
||||||
|
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
|
||||||
|
cl.EXPECT().BlockedServicesSchedule()
|
||||||
|
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
|
||||||
|
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
|
||||||
|
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfigWithIgnored{}, nil)
|
||||||
|
cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
|
||||||
|
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
|
||||||
|
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
|
||||||
|
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)
|
||||||
|
cl.EXPECT().TLSConfig().Return(&model.TlsConfig{}, nil)
|
||||||
|
|
||||||
|
// replica
|
||||||
|
cl.EXPECT().Host().Times(2)
|
||||||
|
cl.EXPECT().Status().Return(&model.ServerStatus{Version: "v0.106.9"}, nil)
|
||||||
|
w.sync()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
44
internal/test/matchers/matchers.go
Normal file
44
internal/test/matchers/matchers.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package matchers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/onsi/gomega/format"
|
||||||
|
"github.com/onsi/gomega/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type equalIgnoringLineEndingsMatcher struct {
|
||||||
|
expected string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (matcher *equalIgnoringLineEndingsMatcher) Match(actual any) (success bool, err error) {
|
||||||
|
actualStr, ok := actual.(string)
|
||||||
|
if !ok {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizedActual := strings.ReplaceAll(actualStr, "\r\n", "\n")
|
||||||
|
normalizedExpected := strings.ReplaceAll(matcher.expected, "\r\n", "\n")
|
||||||
|
|
||||||
|
return normalizedActual == normalizedExpected, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (matcher *equalIgnoringLineEndingsMatcher) FailureMessage(actual any) (message string) {
|
||||||
|
actualString, actualOK := actual.(string)
|
||||||
|
if actualOK {
|
||||||
|
return format.MessageWithDiff(actualString, "to equal", matcher.expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
return format.Message(actual, "to equal", matcher.expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (matcher *equalIgnoringLineEndingsMatcher) NegatedFailureMessage(actual any) (message string) {
|
||||||
|
return format.Message(actual, "not to equal", matcher.expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EqualIgnoringLineEndings returns a new matcher.
|
||||||
|
func EqualIgnoringLineEndings(expected string) types.GomegaMatcher {
|
||||||
|
return &equalIgnoringLineEndingsMatcher{
|
||||||
|
expected: expected,
|
||||||
|
}
|
||||||
|
}
|
||||||
104
internal/types/features.go
Normal file
104
internal/types/features.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewFeatures(enabled bool) Features {
|
||||||
|
return Features{
|
||||||
|
DNS: DNS{
|
||||||
|
AccessLists: enabled,
|
||||||
|
ServerConfig: enabled,
|
||||||
|
Rewrites: enabled,
|
||||||
|
},
|
||||||
|
DHCP: DHCP{
|
||||||
|
ServerConfig: enabled,
|
||||||
|
StaticLeases: enabled,
|
||||||
|
},
|
||||||
|
GeneralSettings: enabled,
|
||||||
|
QueryLogConfig: enabled,
|
||||||
|
StatsConfig: enabled,
|
||||||
|
ClientSettings: enabled,
|
||||||
|
Services: enabled,
|
||||||
|
Filters: enabled,
|
||||||
|
Theme: enabled,
|
||||||
|
TLSConfig: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Features feature flags.
|
||||||
|
type Features struct {
|
||||||
|
DNS DNS `json:"dns" yaml:"dns"`
|
||||||
|
DHCP DHCP `json:"dhcp" yaml:"dhcp"`
|
||||||
|
GeneralSettings bool `json:"generalSettings" yaml:"generalSettings" documentation:"Sync general settings" env:"FEATURES_GENERAL_SETTINGS"`
|
||||||
|
QueryLogConfig bool `json:"queryLogConfig" yaml:"queryLogConfig" documentation:"Sync query log config" env:"FEATURES_QUERY_LOG_CONFIG"`
|
||||||
|
StatsConfig bool `json:"statsConfig" yaml:"statsConfig" documentation:"Sync stats config" env:"FEATURES_STATS_CONFIG"`
|
||||||
|
ClientSettings bool `json:"clientSettings" yaml:"clientSettings" documentation:"Sync client settings" env:"FEATURES_CLIENT_SETTINGS"`
|
||||||
|
Services bool `json:"services" yaml:"services" documentation:"Sync services" env:"FEATURES_SERVICES"`
|
||||||
|
Filters bool `json:"filters" yaml:"filters" documentation:"Sync filters" env:"FEATURES_FILTERS"`
|
||||||
|
Theme bool `json:"theme" yaml:"theme" documentation:"Sync the web UI theme" env:"FEATURES_THEME"`
|
||||||
|
TLSConfig bool `json:"tlsConfig" yaml:"tlsConfig" documentation:"Sync the TLS config" env:"FEATURES_TLS_CONFIG"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DHCP features.
|
||||||
|
type DHCP struct {
|
||||||
|
ServerConfig bool `documentation:"Sync DHCP server config" env:"FEATURES_DHCP_SERVER_CONFIG" json:"serverConfig" yaml:"serverConfig"`
|
||||||
|
StaticLeases bool `documentation:"Sync DHCP static leases" env:"FEATURES_DHCP_STATIC_LEASES" json:"staticLeases" yaml:"staticLeases"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNS features.
|
||||||
|
type DNS struct {
|
||||||
|
AccessLists bool `documentation:"Sync DNS access lists" env:"FEATURES_DNS_ACCESS_LISTS" json:"accessLists" yaml:"accessLists"`
|
||||||
|
ServerConfig bool `documentation:"Sync DNS server config" env:"FEATURES_DNS_SERVER_CONFIG" json:"serverConfig" yaml:"serverConfig"`
|
||||||
|
Rewrites bool `documentation:"Sync DNS rewrites" env:"FEATURES_DNS_REWRITES" json:"rewrites" yaml:"rewrites"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogDisabled log all disabled features.
|
||||||
|
func (f *Features) LogDisabled(l *zap.SugaredLogger) {
|
||||||
|
features := f.collectDisabled()
|
||||||
|
|
||||||
|
if len(features) > 0 {
|
||||||
|
l.With("features", features).Info("Disabled features")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Features) collectDisabled() []string {
|
||||||
|
var features []string
|
||||||
|
if !f.DHCP.ServerConfig {
|
||||||
|
features = append(features, "DHCP.ServerConfig")
|
||||||
|
}
|
||||||
|
if !f.DHCP.StaticLeases {
|
||||||
|
features = append(features, "DHCP.StaticLeases")
|
||||||
|
}
|
||||||
|
if !f.DNS.AccessLists {
|
||||||
|
features = append(features, "DNS.AccessLists")
|
||||||
|
}
|
||||||
|
if !f.DNS.ServerConfig {
|
||||||
|
features = append(features, "DNS.ServerConfig")
|
||||||
|
}
|
||||||
|
if !f.DNS.Rewrites {
|
||||||
|
features = append(features, "DNS.Rewrites")
|
||||||
|
}
|
||||||
|
if !f.GeneralSettings {
|
||||||
|
features = append(features, "GeneralSettings")
|
||||||
|
}
|
||||||
|
if !f.QueryLogConfig {
|
||||||
|
features = append(features, "QueryLogConfig")
|
||||||
|
}
|
||||||
|
if !f.StatsConfig {
|
||||||
|
features = append(features, "StatsConfig")
|
||||||
|
}
|
||||||
|
if !f.ClientSettings {
|
||||||
|
features = append(features, "ClientSettings")
|
||||||
|
}
|
||||||
|
if !f.Services {
|
||||||
|
features = append(features, "BlockedServices")
|
||||||
|
}
|
||||||
|
if !f.Filters {
|
||||||
|
features = append(features, "Filters")
|
||||||
|
}
|
||||||
|
if !f.TLSConfig {
|
||||||
|
features = append(features, "TLSConfig")
|
||||||
|
}
|
||||||
|
return features
|
||||||
|
}
|
||||||
222
internal/types/types.go
Normal file
222
internal/types/types.go
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
// Package types
|
||||||
|
// +kubebuilder:object:generate=true
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultAPIPath default api path.
|
||||||
|
DefaultAPIPath = "/control"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config application configuration struct
|
||||||
|
// +k8s:deepcopy-gen=true
|
||||||
|
type Config struct {
|
||||||
|
// Origin adguardhome instance
|
||||||
|
Origin *AdGuardInstance `json:"origin" yaml:"origin"`
|
||||||
|
// One single replica adguardhome instance
|
||||||
|
Replica *AdGuardInstance `json:"replica,omitempty" yaml:"replica,omitempty"`
|
||||||
|
// Multiple replica instances
|
||||||
|
Replicas []AdGuardInstance `json:"replicas,omitempty" yaml:"replicas,omitempty" faker:"slice_len=2"`
|
||||||
|
Cron string `json:"cron,omitempty" yaml:"cron,omitempty" documentation:"Cron expression for the sync interval" env:"CRON"`
|
||||||
|
RunOnStart bool `json:"runOnStart,omitempty" yaml:"runOnStart,omitempty" documentation:"Run the sync on startup" env:"RUN_ON_START"`
|
||||||
|
PrintConfigOnly bool `json:"printConfigOnly,omitempty" yaml:"printConfigOnly,omitempty" documentation:"Print current config only and stop the application" env:"PRINT_CONFIG_ONLY"`
|
||||||
|
ContinueOnError bool `json:"continueOnError,omitempty" yaml:"continueOnError,omitempty" documentation:"Continue sync on errors" env:"CONTINUE_ON_ERROR"`
|
||||||
|
API API `json:"api,omitempty" yaml:"api,omitempty"`
|
||||||
|
Features Features `json:"features,omitempty" yaml:"features,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// API configuration.
|
||||||
|
type API struct {
|
||||||
|
Port int `documentation:"API port (API is disabled if port is set to 0)" env:"API_PORT" json:"port,omitempty" yaml:"port,omitempty"`
|
||||||
|
Username string `documentation:"API username" env:"API_USERNAME" json:"username,omitempty" yaml:"username,omitempty"`
|
||||||
|
Password string `documentation:"API password" env:"API_PASSWORD" json:"password,omitempty" yaml:"password,omitempty"`
|
||||||
|
DarkMode bool `documentation:"API dark mode" env:"API_DARK_MODE" json:"darkMode,omitempty" yaml:"darkMode,omitempty"`
|
||||||
|
Metrics Metrics ` json:"metrics,omitempty" yaml:"metrics,omitempty"`
|
||||||
|
TLS TLS ` json:"tls,omitempty" yaml:"tls,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metrics configuration.
|
||||||
|
type Metrics struct {
|
||||||
|
Enabled bool `documentation:"Enable metrics" env:"API_METRICS_ENABLED" json:"enabled,omitempty" yaml:"enabled,omitempty"`
|
||||||
|
ScrapeInterval time.Duration `documentation:"Interval for metrics scraping" env:"API_METRICS_SCRAPE_INTERVAL" json:"scrapeInterval,omitempty" yaml:"scrapeInterval,omitempty"`
|
||||||
|
QueryLogLimit int `documentation:"Metrics log query limit" env:"API_METRICS_QUERY_LOG_LIMIT" json:"queryLogLimit,omitempty" yaml:"queryLogLimit,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLS configuration.
|
||||||
|
type TLS struct {
|
||||||
|
CertDir string `documentation:"API TLS certificate directory" env:"API_TLS_CERT_DIR" json:"certDir,omitempty" yaml:"certDir,omitempty"`
|
||||||
|
CertName string `documentation:"API TLS certificate file name" env:"API_TLS_CERT_NAME" json:"certName,omitempty" yaml:"certName,omitempty"`
|
||||||
|
KeyName string `documentation:"API TLS key file name" env:"API_TLS_KEY_NAME" json:"keyName,omitempty" yaml:"keyName,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TLS) Enabled() bool {
|
||||||
|
return strings.TrimSpace(t.CertDir) != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TLS) Certs() (cert, key string) {
|
||||||
|
cert = filepath.Join(t.CertDir, defaultIfEmpty(t.CertName, "tls.crt"))
|
||||||
|
key = filepath.Join(t.CertDir, defaultIfEmpty(t.KeyName, "tls.key"))
|
||||||
|
return cert, key
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultIfEmpty(val, fallback string) string {
|
||||||
|
if strings.TrimSpace(val) == "" {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mask maks username and password.
|
||||||
|
func (a *API) Mask() {
|
||||||
|
a.Username = mask(a.Username)
|
||||||
|
a.Password = mask(a.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UniqueReplicas get unique replication instances.
|
||||||
|
func (cfg *Config) UniqueReplicas() []AdGuardInstance {
|
||||||
|
dedup := make(map[string]AdGuardInstance)
|
||||||
|
if cfg.Replica != nil && cfg.Replica.URL != "" {
|
||||||
|
if cfg.Replica.APIPath == "" {
|
||||||
|
cfg.Replica.APIPath = DefaultAPIPath
|
||||||
|
}
|
||||||
|
dedup[cfg.Replica.Key()] = *cfg.Replica
|
||||||
|
}
|
||||||
|
for _, replica := range cfg.Replicas {
|
||||||
|
if replica.APIPath == "" {
|
||||||
|
replica.APIPath = DefaultAPIPath
|
||||||
|
}
|
||||||
|
if replica.URL != "" {
|
||||||
|
dedup[replica.Key()] = replica
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var r []AdGuardInstance
|
||||||
|
for _, replica := range dedup {
|
||||||
|
r = append(r, replica)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the current config.
|
||||||
|
func (cfg *Config) Log(l *zap.SugaredLogger) {
|
||||||
|
c := cfg.mask()
|
||||||
|
l.With("config", c).Debug("Using config")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) mask() *Config {
|
||||||
|
c := cfg.DeepCopy()
|
||||||
|
c.Origin.Mask()
|
||||||
|
if c.Replica != nil {
|
||||||
|
if c.Replica.URL == "" {
|
||||||
|
c.Replica = nil
|
||||||
|
} else {
|
||||||
|
c.Replica.Mask()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := range c.Replicas {
|
||||||
|
c.Replicas[i].Mask()
|
||||||
|
}
|
||||||
|
c.API.Mask()
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) Init() error {
|
||||||
|
if err := cfg.Origin.Init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := range cfg.Replicas {
|
||||||
|
replica := &cfg.Replicas[i]
|
||||||
|
if err := replica.Init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdGuardInstance AdguardHome config instance
|
||||||
|
// +k8s:deepcopy-gen=true
|
||||||
|
type AdGuardInstance struct {
|
||||||
|
URL string `documentation:"URL of adguardhome instance" env:"URL" faker:"url" json:"url" yaml:"url"`
|
||||||
|
WebURL string `documentation:"Web URL of adguardhome instance" env:"WEB_URL" faker:"url" json:"webURL" yaml:"webURL"`
|
||||||
|
APIPath string `documentation:"API Path" env:"API_PATH" json:"apiPath,omitempty" yaml:"apiPath,omitempty"`
|
||||||
|
Username string `documentation:"Adguardhome username" env:"USERNAME" json:"username,omitempty" yaml:"username,omitempty"`
|
||||||
|
Password string `documentation:"Adguardhome password" env:"PASSWORD" json:"password,omitempty" yaml:"password,omitempty"`
|
||||||
|
Cookie string `documentation:"Adguardhome cookie" env:"COOKIE" json:"cookie,omitempty" yaml:"cookie,omitempty"`
|
||||||
|
RequestHeaders map[string]string `documentation:"Request Headers 'key1:value1,key2:value2'" env:"REQUEST_HEADERS" json:"requestHeaders,omitempty" yaml:"requestHeaders,omitempty"`
|
||||||
|
InsecureSkipVerify bool `documentation:"Skip TLS verification" env:"INSECURE_SKIP_VERIFY" json:"insecureSkipVerify" yaml:"insecureSkipVerify"`
|
||||||
|
AutoSetup bool `documentation:"Automatically setup the instance if it is not initialized" env:"AUTO_SETUP" json:"autoSetup" yaml:"autoSetup"`
|
||||||
|
InterfaceName string `documentation:"Network interface name" env:"INTERFACE_NAME" json:"interfaceName,omitempty" yaml:"interfaceName,omitempty"`
|
||||||
|
DHCPServerEnabled *bool `documentation:"Enable DHCP server" env:"DHCP_SERVER_ENABLED" json:"dhcpServerEnabled,omitempty" yaml:"dhcpServerEnabled,omitempty"`
|
||||||
|
|
||||||
|
Host string `json:"-" yaml:"-"`
|
||||||
|
WebHost string `json:"-" yaml:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key AdGuardInstance key.
|
||||||
|
func (i *AdGuardInstance) Key() string {
|
||||||
|
return fmt.Sprintf("%s#%s", i.URL, i.APIPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mask maks username and password.
|
||||||
|
func (i *AdGuardInstance) Mask() {
|
||||||
|
i.Username = mask(i.Username)
|
||||||
|
i.Password = mask(i.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *AdGuardInstance) Init() error {
|
||||||
|
u, err := url.Parse(i.URL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.Host = u.Host
|
||||||
|
|
||||||
|
if i.WebURL == "" {
|
||||||
|
i.WebHost = i.Host
|
||||||
|
i.WebURL = i.URL
|
||||||
|
} else {
|
||||||
|
u, err := url.Parse(i.WebURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.WebHost = u.Host
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mask(s string) string {
|
||||||
|
if len(s) < 3 {
|
||||||
|
return strings.Repeat("*", len(s))
|
||||||
|
}
|
||||||
|
mask := strings.Repeat("*", len(s)-2)
|
||||||
|
return fmt.Sprintf("%v%s%v", string(s[0]), mask, string(s[len(s)-1]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protection API struct.
|
||||||
|
type Protection struct {
|
||||||
|
ProtectionEnabled bool `json:"protection_enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstallConfig AdguardHome install config.
|
||||||
|
type InstallConfig struct {
|
||||||
|
Web InstallPort `json:"web"`
|
||||||
|
DNS InstallPort `json:"dns"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstallPort AdguardHome install config port.
|
||||||
|
type InstallPort struct {
|
||||||
|
IP string `json:"ip"`
|
||||||
|
Port int `json:"port"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
CanAutofix bool `json:"can_autofix"`
|
||||||
|
}
|
||||||
15
internal/types/types_fuzz_test.go
Normal file
15
internal/types/types_fuzz_test.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FuzzMask(f *testing.F) {
|
||||||
|
testcases := []string{"", "a", "ab", "abc", "abcd"}
|
||||||
|
for _, tc := range testcases {
|
||||||
|
f.Add(tc)
|
||||||
|
}
|
||||||
|
f.Fuzz(func(t *testing.T, value string) {
|
||||||
|
_ = mask(value)
|
||||||
|
})
|
||||||
|
}
|
||||||
13
internal/types/types_suite_test.go
Normal file
13
internal/types/types_suite_test.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package types_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSync(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Types Suite")
|
||||||
|
}
|
||||||
152
internal/types/types_test.go
Normal file
152
internal/types/types_test.go
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Types", func() {
|
||||||
|
Context("AdGuardInstance", func() {
|
||||||
|
var inst AdGuardInstance
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
inst = AdGuardInstance{}
|
||||||
|
})
|
||||||
|
Context("Instance Init", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
inst.URL = "https://localhost:3000"
|
||||||
|
})
|
||||||
|
It("should correctly set Host and WebHost if only URL is set", func() {
|
||||||
|
err := inst.Init()
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(inst.Host).Should(Equal("localhost:3000"))
|
||||||
|
Ω(inst.WebHost).Should(Equal("localhost:3000"))
|
||||||
|
Ω(inst.URL).Should(Equal("https://localhost:3000"))
|
||||||
|
Ω(inst.WebURL).Should(Equal("https://localhost:3000"))
|
||||||
|
})
|
||||||
|
It("should correctly set Host and WebHost if URL and WebURL are set", func() {
|
||||||
|
inst.WebURL = "https://127.0.0.1:4000"
|
||||||
|
err := inst.Init()
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(inst.Host).Should(Equal("localhost:3000"))
|
||||||
|
Ω(inst.WebHost).Should(Equal("127.0.0.1:4000"))
|
||||||
|
Ω(inst.WebURL).Should(Equal(inst.WebURL))
|
||||||
|
Ω(inst.URL).Should(Equal("https://localhost:3000"))
|
||||||
|
Ω(inst.WebURL).Should(Equal("https://127.0.0.1:4000"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("Config", func() {
|
||||||
|
Context("init", func() {
|
||||||
|
cfg := Config{
|
||||||
|
Origin: &AdGuardInstance{},
|
||||||
|
Replicas: []AdGuardInstance{
|
||||||
|
{URL: "https://localhost:3000"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := cfg.Init()
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Replicas[0].Host).Should(Equal("localhost:3000"))
|
||||||
|
Ω(cfg.Replicas[0].WebHost).Should(Equal("localhost:3000"))
|
||||||
|
Ω(cfg.Replicas[0].URL).Should(Equal("https://localhost:3000"))
|
||||||
|
Ω(cfg.Replicas[0].WebURL).Should(Equal("https://localhost:3000"))
|
||||||
|
})
|
||||||
|
Context("UniqueReplicas", func() {
|
||||||
|
It("should return unique replicas in the array", func() {
|
||||||
|
cfg := Config{
|
||||||
|
Origin: &AdGuardInstance{},
|
||||||
|
Replicas: []AdGuardInstance{
|
||||||
|
{URL: "a"},
|
||||||
|
{URL: "a", APIPath: DefaultAPIPath},
|
||||||
|
{URL: "a", APIPath: "foo"},
|
||||||
|
{URL: "b", APIPath: DefaultAPIPath},
|
||||||
|
},
|
||||||
|
Replica: &AdGuardInstance{URL: "b"},
|
||||||
|
}
|
||||||
|
replicas := cfg.UniqueReplicas()
|
||||||
|
Ω(replicas).Should(HaveLen(3))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("mask", func() {
|
||||||
|
It("should mask all names and passwords", func() {
|
||||||
|
cfg := Config{
|
||||||
|
Origin: &AdGuardInstance{},
|
||||||
|
Replicas: []AdGuardInstance{
|
||||||
|
{URL: "a", Username: "user", Password: "pass"},
|
||||||
|
},
|
||||||
|
Replica: &AdGuardInstance{URL: "a", Username: "user", Password: "pass"},
|
||||||
|
API: API{Username: "user", Password: "pass"},
|
||||||
|
}
|
||||||
|
masked := cfg.mask()
|
||||||
|
Ω(masked.Replicas[0].Username).Should(Equal("u**r"))
|
||||||
|
Ω(masked.Replicas[0].Password).Should(Equal("p**s"))
|
||||||
|
Ω(masked.Replica.Username).Should(Equal("u**r"))
|
||||||
|
Ω(masked.Replica.Password).Should(Equal("p**s"))
|
||||||
|
Ω(masked.API.Username).Should(Equal("u**r"))
|
||||||
|
Ω(masked.API.Password).Should(Equal("p**s"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
DescribeTable("mask should work correctly",
|
||||||
|
func(value, expected string) {
|
||||||
|
Ω(mask(value)).Should(Equal(expected))
|
||||||
|
},
|
||||||
|
Entry(`Empty password`, "", ""),
|
||||||
|
Entry(`1 char password`, "a", "*"),
|
||||||
|
Entry(`2 char password`, "ab", "**"),
|
||||||
|
Entry(`3 char password`, "abc", "a*c"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
Context("Feature", func() {
|
||||||
|
Context("LogDisabled", func() {
|
||||||
|
It("should log all features", func() {
|
||||||
|
f := NewFeatures(false)
|
||||||
|
Ω(f.collectDisabled()).Should(HaveLen(12))
|
||||||
|
})
|
||||||
|
It("should log no features", func() {
|
||||||
|
f := NewFeatures(true)
|
||||||
|
Ω(f.collectDisabled()).Should(HaveLen(1))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("TLS", func() {
|
||||||
|
var t TLS
|
||||||
|
BeforeEach(func() {
|
||||||
|
t = TLS{
|
||||||
|
CertDir: "/path/to/certs",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Context("Enabled", func() {
|
||||||
|
It("should use enabled", func() {
|
||||||
|
Ω(t.Enabled()).Should(BeTrue())
|
||||||
|
})
|
||||||
|
It("should use disabled", func() {
|
||||||
|
t.CertDir = " "
|
||||||
|
Ω(t.Enabled()).Should(BeFalse())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("Certs", func() {
|
||||||
|
It("should use default crt and key", func() {
|
||||||
|
crt, key := t.Certs()
|
||||||
|
crt = normalizePath(crt)
|
||||||
|
key = normalizePath(key)
|
||||||
|
Ω(crt).Should(Equal("/path/to/certs/tls.crt"))
|
||||||
|
Ω(key).Should(Equal("/path/to/certs/tls.key"))
|
||||||
|
})
|
||||||
|
It("should use custom crt and key", func() {
|
||||||
|
t.CertName = "foo.crt"
|
||||||
|
t.KeyName = "bar.key"
|
||||||
|
crt, key := t.Certs()
|
||||||
|
crt = normalizePath(crt)
|
||||||
|
key = normalizePath(key)
|
||||||
|
Ω(crt).Should(Equal("/path/to/certs/foo.crt"))
|
||||||
|
Ω(key).Should(Equal("/path/to/certs/bar.key"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
func normalizePath(path string) string {
|
||||||
|
return strings.ReplaceAll(path, "\\", "/")
|
||||||
|
}
|
||||||
209
internal/types/zz_generated.deepcopy.go
Normal file
209
internal/types/zz_generated.deepcopy.go
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
//go:build !ignore_autogenerated
|
||||||
|
|
||||||
|
// Code generated by controller-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import ()
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *API) DeepCopyInto(out *API) {
|
||||||
|
*out = *in
|
||||||
|
out.Metrics = in.Metrics
|
||||||
|
out.TLS = in.TLS
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new API.
|
||||||
|
func (in *API) DeepCopy() *API {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(API)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *AdGuardInstance) DeepCopyInto(out *AdGuardInstance) {
|
||||||
|
*out = *in
|
||||||
|
if in.RequestHeaders != nil {
|
||||||
|
in, out := &in.RequestHeaders, &out.RequestHeaders
|
||||||
|
*out = make(map[string]string, len(*in))
|
||||||
|
for key, val := range *in {
|
||||||
|
(*out)[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if in.DHCPServerEnabled != nil {
|
||||||
|
in, out := &in.DHCPServerEnabled, &out.DHCPServerEnabled
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdGuardInstance.
|
||||||
|
func (in *AdGuardInstance) DeepCopy() *AdGuardInstance {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(AdGuardInstance)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Config) DeepCopyInto(out *Config) {
|
||||||
|
*out = *in
|
||||||
|
if in.Origin != nil {
|
||||||
|
in, out := &in.Origin, &out.Origin
|
||||||
|
*out = new(AdGuardInstance)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.Replica != nil {
|
||||||
|
in, out := &in.Replica, &out.Replica
|
||||||
|
*out = new(AdGuardInstance)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.Replicas != nil {
|
||||||
|
in, out := &in.Replicas, &out.Replicas
|
||||||
|
*out = make([]AdGuardInstance, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.API = in.API
|
||||||
|
out.Features = in.Features
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Config.
|
||||||
|
func (in *Config) DeepCopy() *Config {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Config)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *DHCP) DeepCopyInto(out *DHCP) {
|
||||||
|
*out = *in
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DHCP.
|
||||||
|
func (in *DHCP) DeepCopy() *DHCP {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(DHCP)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *DNS) DeepCopyInto(out *DNS) {
|
||||||
|
*out = *in
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNS.
|
||||||
|
func (in *DNS) DeepCopy() *DNS {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(DNS)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Features) DeepCopyInto(out *Features) {
|
||||||
|
*out = *in
|
||||||
|
out.DNS = in.DNS
|
||||||
|
out.DHCP = in.DHCP
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Features.
|
||||||
|
func (in *Features) DeepCopy() *Features {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Features)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *InstallConfig) DeepCopyInto(out *InstallConfig) {
|
||||||
|
*out = *in
|
||||||
|
out.Web = in.Web
|
||||||
|
out.DNS = in.DNS
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstallConfig.
|
||||||
|
func (in *InstallConfig) DeepCopy() *InstallConfig {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(InstallConfig)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *InstallPort) DeepCopyInto(out *InstallPort) {
|
||||||
|
*out = *in
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstallPort.
|
||||||
|
func (in *InstallPort) DeepCopy() *InstallPort {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(InstallPort)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Metrics) DeepCopyInto(out *Metrics) {
|
||||||
|
*out = *in
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Metrics.
|
||||||
|
func (in *Metrics) DeepCopy() *Metrics {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Metrics)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Protection) DeepCopyInto(out *Protection) {
|
||||||
|
*out = *in
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Protection.
|
||||||
|
func (in *Protection) DeepCopy() *Protection {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Protection)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *TLS) DeepCopyInto(out *TLS) {
|
||||||
|
*out = *in
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLS.
|
||||||
|
func (in *TLS) DeepCopy() *TLS {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(TLS)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
18
internal/utils/clone.go
Normal file
18
internal/utils/clone.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Clone[I any](in, out I) I {
|
||||||
|
b, _ := json.Marshal(in)
|
||||||
|
_ = json.Unmarshal(b, out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func JSONEquals(a, b any) bool {
|
||||||
|
ja, _ := json.Marshal(a)
|
||||||
|
jb, _ := json.Marshal(b)
|
||||||
|
return bytes.Equal(ja, jb)
|
||||||
|
}
|
||||||
24
internal/utils/ptr.go
Normal file
24
internal/utils/ptr.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func Ptr[I any](i I) *I {
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
|
||||||
|
func PtrToString[I any](i *I) string {
|
||||||
|
if i == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%v", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PtrEquals[I comparable](a, b *I) bool {
|
||||||
|
if a == nil && b == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if a == nil || b == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return *a == *b
|
||||||
|
}
|
||||||
23
internal/versions/versions.go
Normal file
23
internal/versions/versions.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package versions
|
||||||
|
|
||||||
|
import "golang.org/x/mod/semver"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MinAgh minimal adguardhome version.
|
||||||
|
MinAgh = "v0.107.40"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IsNewerThan(v1, v2 string) bool {
|
||||||
|
return semver.Compare(sanitize(v1), sanitize(v2)) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsSame(v1, v2 string) bool {
|
||||||
|
return semver.Compare(sanitize(v1), sanitize(v2)) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func sanitize(v string) string {
|
||||||
|
if v == "" || v[0] == 'v' {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return "v" + v
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package types_test
|
package versions_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
||||||
30
internal/versions/versions_test.go
Normal file
30
internal/versions/versions_test.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package versions_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/internal/versions"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Versions", func() {
|
||||||
|
Context("IsNewerThan", func() {
|
||||||
|
It("should correctly parse json", func() {
|
||||||
|
Ω(versions.IsNewerThan("v0.106.10", "v0.106.9")).Should(BeTrue())
|
||||||
|
Ω(versions.IsNewerThan("v0.106.9", "v0.106.10")).Should(BeFalse())
|
||||||
|
Ω(versions.IsNewerThan("v0.106.10", "0.106.9")).Should(BeTrue())
|
||||||
|
Ω(versions.IsNewerThan("v0.106.9", "0.106.10")).Should(BeFalse())
|
||||||
|
// tests for #607
|
||||||
|
Ω(versions.IsNewerThan("v0.108.0-b.72", versions.MinAgh)).Should(BeTrue())
|
||||||
|
Ω(versions.IsNewerThan("0.108.0-b.72", versions.MinAgh)).Should(BeTrue())
|
||||||
|
Ω(versions.IsNewerThan(versions.MinAgh, "v0.108.0-b.72")).Should(BeFalse())
|
||||||
|
Ω(versions.IsNewerThan(versions.MinAgh, "0.108.0-b.72")).Should(BeFalse())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("IsSame", func() {
|
||||||
|
It("should be the same version", func() {
|
||||||
|
Ω(versions.IsSame("v0.106.9", "v0.106.9")).Should(BeTrue())
|
||||||
|
Ω(versions.IsSame("0.106.9", "v0.106.9")).Should(BeTrue())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
1
media/adguardhome-sync.svg
Normal file
1
media/adguardhome-sync.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" id="svg3" version="1.2" viewBox="0 0 1000 1000"><defs id="defs3"><linearGradient id="swatch26"><stop style="stop-color:#000;stop-opacity:1" id="stop26" offset="0"/></linearGradient><linearGradient id="swatch25"><stop style="stop-color:#407b28;stop-opacity:1" id="stop25" offset="0"/></linearGradient></defs><path id="path1" fill="#68bc71" d="m 993.75002,114.1 c 0,171.8 3.1,595.3 -493.8,885.9 C 3.0500233,709.4 6.2500233,285.9 6.2500233,114.1 159.35002,35.9 345.25002,0 499.95002,0 c 154.7,0 340.6,35.9 493.8,114.1 z"/><path id="path2" fill="#67b279" d="M500 1000C3.1 709.4 6.2 285.9 6.2 114.1 159.4 35.9 345.3 0 500 0z"/><path id="path3" fill="#fff" d="m 225,449.6 c 15,-11.7 80,-53.4 128.3,1.6 L 453.3,569.6 720,297.9 c 11.7,-10 31.7,-23.3 55,-5 L 455,716.2 Z" style="display:none"/><circle style="display:none;fill:#fff;fill-opacity:.5;stroke:#fff;stroke-width:8e-08;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:100;stroke-dasharray:none;stroke-opacity:.502604" id="path18" cx="-500" cy="426" r="400" transform="scale(-1,1)"/><path id="path12-8" style="fill:#fff;fill-opacity:1;stroke:#fff;stroke-width:4.00001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:100;stroke-dasharray:none;stroke-opacity:1" d="m -75.867249,555.75073 c -1.469322,-39.92741 -12.296964,-76.43624 -30.823861,-104.61661 -21.05517,28.18037 -47.89663,45.38022 -75.18829,53.68483 l 42.51192,20.42415 c -33.35434,61.94297 -101.60764,105.3289 -167.51689,101.23666 -42.42317,-1.43193 -82.3318,-22.18867 -112.01355,-52.33871 -21.09944,-21.43232 -53.37423,-31.44115 -79.38397,-16.1329 l -10.3958,6.11853 c 43.49744,68.86447 120.54256,113.80911 201.79332,112.04834 87.97224,-4.41047 162.60077,-62.73987 194.96448,-137.74518 z M -537.90127,346.51705 c 1.46924,39.92741 12.29681,76.43626 30.82364,104.61667 21.05523,-28.18031 47.89674,-45.38011 75.18842,-53.68467 l -42.51189,-20.42423 c 33.35441,-61.94281 101.60759,-105.32857 167.51673,-101.23635 42.4233,1.43194 82.33203,22.18879 112.01381,52.33897 21.0994,21.43235 53.37417,31.44124 79.38393,16.13305 l 10.39581,-6.11852 c -43.49739,-68.86468 -120.54264,-113.8095 -201.79355,-112.04872 -87.97208,4.41046 -162.60049,62.73965 -194.96429,137.74477 z" transform="matrix(0,-1.3167476,1.3167476,0,-94.029776,21.910887)"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -1,453 +0,0 @@
|
|||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/log"
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
l = log.GetLogger("client")
|
|
||||||
SetupNeededError = errors.New("setup needed")
|
|
||||||
)
|
|
||||||
|
|
||||||
// New create a new client
|
|
||||||
func New(config types.AdGuardInstance) (Client, error) {
|
|
||||||
var apiURL string
|
|
||||||
if config.APIPath == "" {
|
|
||||||
apiURL = fmt.Sprintf("%s/control", config.URL)
|
|
||||||
} else {
|
|
||||||
apiURL = fmt.Sprintf("%s/%s", config.URL, config.APIPath)
|
|
||||||
}
|
|
||||||
u, err := url.Parse(apiURL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
u.Path = path.Clean(u.Path)
|
|
||||||
cl := resty.New().SetHostURL(u.String()).SetDisableWarn(true)
|
|
||||||
|
|
||||||
if config.InsecureSkipVerify {
|
|
||||||
cl.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Username != "" && config.Password != "" {
|
|
||||||
cl = cl.SetBasicAuth(config.Username, config.Password)
|
|
||||||
}
|
|
||||||
|
|
||||||
// no redirect
|
|
||||||
cl.SetRedirectPolicy(resty.NoRedirectPolicy())
|
|
||||||
|
|
||||||
return &client{
|
|
||||||
host: u.Host,
|
|
||||||
client: cl,
|
|
||||||
log: l.With("host", u.Host),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client AdguardHome API client interface
|
|
||||||
type Client interface {
|
|
||||||
Host() string
|
|
||||||
|
|
||||||
Status() (*types.Status, error)
|
|
||||||
ToggleProtection(enable bool) error
|
|
||||||
RewriteList() (*types.RewriteEntries, error)
|
|
||||||
AddRewriteEntries(e ...types.RewriteEntry) error
|
|
||||||
DeleteRewriteEntries(e ...types.RewriteEntry) error
|
|
||||||
|
|
||||||
Filtering() (*types.FilteringStatus, error)
|
|
||||||
ToggleFiltering(enabled bool, interval int) error
|
|
||||||
AddFilters(whitelist bool, e ...types.Filter) error
|
|
||||||
DeleteFilters(whitelist bool, e ...types.Filter) error
|
|
||||||
UpdateFilters(whitelist bool, e ...types.Filter) error
|
|
||||||
RefreshFilters(whitelist bool) error
|
|
||||||
SetCustomRules(rules types.UserRules) error
|
|
||||||
|
|
||||||
SafeBrowsing() (bool, error)
|
|
||||||
ToggleSafeBrowsing(enable bool) error
|
|
||||||
Parental() (bool, error)
|
|
||||||
ToggleParental(enable bool) error
|
|
||||||
SafeSearch() (bool, error)
|
|
||||||
ToggleSafeSearch(enable bool) error
|
|
||||||
|
|
||||||
Services() (types.Services, error)
|
|
||||||
SetServices(services types.Services) error
|
|
||||||
|
|
||||||
Clients() (*types.Clients, error)
|
|
||||||
AddClients(client ...types.Client) error
|
|
||||||
UpdateClients(client ...types.Client) error
|
|
||||||
DeleteClients(client ...types.Client) error
|
|
||||||
|
|
||||||
QueryLogConfig() (*types.QueryLogConfig, error)
|
|
||||||
SetQueryLogConfig(enabled bool, interval int, anonymizeClientIP bool) error
|
|
||||||
StatsConfig() (*types.IntervalConfig, error)
|
|
||||||
SetStatsConfig(interval int) error
|
|
||||||
Setup() error
|
|
||||||
|
|
||||||
AccessList() (*types.AccessList, error)
|
|
||||||
SetAccessList(*types.AccessList) error
|
|
||||||
|
|
||||||
DNSConfig() (*types.DNSConfig, error)
|
|
||||||
SetDNSConfig(*types.DNSConfig) error
|
|
||||||
|
|
||||||
DHCPServerConfig() (*types.DHCPServerConfig, error)
|
|
||||||
SetDHCPServerConfig(*types.DHCPServerConfig) error
|
|
||||||
AddDHCPStaticLeases(leases ...types.Lease) error
|
|
||||||
DeleteDHCPStaticLeases(leases ...types.Lease) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type client struct {
|
|
||||||
client *resty.Client
|
|
||||||
log *zap.SugaredLogger
|
|
||||||
host string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) Host() string {
|
|
||||||
return cl.host
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) doGet(req *resty.Request, url string) error {
|
|
||||||
rl := cl.log.With("method", "GET", "path", url)
|
|
||||||
if cl.client.UserInfo != nil {
|
|
||||||
rl = rl.With("username", cl.client.UserInfo.Username)
|
|
||||||
}
|
|
||||||
rl.Debug("do get")
|
|
||||||
resp, err := req.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
if resp != nil && resp.StatusCode() == http.StatusFound {
|
|
||||||
loc := resp.Header().Get("Location")
|
|
||||||
if loc == "/install.html" {
|
|
||||||
return SetupNeededError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rl.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err).Debug("error in do get")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rl.With("status", resp.StatusCode(), "body", string(resp.Body())).Debug("got response")
|
|
||||||
if resp.StatusCode() != http.StatusOK {
|
|
||||||
return errors.New(resp.Status())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) doPost(req *resty.Request, url string) error {
|
|
||||||
rl := cl.log.With("method", "POST", "path", url)
|
|
||||||
if cl.client.UserInfo != nil {
|
|
||||||
rl = rl.With("username", cl.client.UserInfo.Username)
|
|
||||||
}
|
|
||||||
rl.Debug("do post")
|
|
||||||
resp, err := req.Post(url)
|
|
||||||
if err != nil {
|
|
||||||
rl.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err).Debug("error in do post")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rl.With("status", resp.StatusCode(), "body", string(resp.Body())).Debug("got response")
|
|
||||||
if resp.StatusCode() != http.StatusOK {
|
|
||||||
return errors.New(resp.Status())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) Status() (*types.Status, error) {
|
|
||||||
status := &types.Status{}
|
|
||||||
err := cl.doGet(cl.client.R().EnableTrace().SetResult(status), "status")
|
|
||||||
return status, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) RewriteList() (*types.RewriteEntries, error) {
|
|
||||||
rewrites := &types.RewriteEntries{}
|
|
||||||
err := cl.doGet(cl.client.R().EnableTrace().SetResult(&rewrites), "/rewrite/list")
|
|
||||||
return rewrites, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) AddRewriteEntries(entries ...types.RewriteEntry) error {
|
|
||||||
for _, e := range entries {
|
|
||||||
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Add rewrite entry")
|
|
||||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&e), "/rewrite/add")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) DeleteRewriteEntries(entries ...types.RewriteEntry) error {
|
|
||||||
for _, e := range entries {
|
|
||||||
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Delete rewrite entry")
|
|
||||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&e), "/rewrite/delete")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) SafeBrowsing() (bool, error) {
|
|
||||||
return cl.toggleStatus("safebrowsing")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) ToggleSafeBrowsing(enable bool) error {
|
|
||||||
return cl.toggleBool("safebrowsing", enable)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) Parental() (bool, error) {
|
|
||||||
return cl.toggleStatus("parental")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) ToggleParental(enable bool) error {
|
|
||||||
return cl.toggleBool("parental", enable)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) SafeSearch() (bool, error) {
|
|
||||||
return cl.toggleStatus("safesearch")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) ToggleSafeSearch(enable bool) error {
|
|
||||||
return cl.toggleBool("safesearch", enable)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) toggleStatus(mode string) (bool, error) {
|
|
||||||
fs := &types.EnableConfig{}
|
|
||||||
err := cl.doGet(cl.client.R().EnableTrace().SetResult(fs), fmt.Sprintf("/%s/status", mode))
|
|
||||||
return fs.Enabled, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) toggleBool(mode string, enable bool) error {
|
|
||||||
cl.log.With("enable", enable).Info(fmt.Sprintf("Toggle %s", mode))
|
|
||||||
var target string
|
|
||||||
if enable {
|
|
||||||
target = "enable"
|
|
||||||
} else {
|
|
||||||
target = "disable"
|
|
||||||
}
|
|
||||||
return cl.doPost(cl.client.R().EnableTrace(), fmt.Sprintf("/%s/%s", mode, target))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) Filtering() (*types.FilteringStatus, error) {
|
|
||||||
f := &types.FilteringStatus{}
|
|
||||||
err := cl.doGet(cl.client.R().EnableTrace().SetResult(f), "/filtering/status")
|
|
||||||
return f, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) AddFilters(whitelist bool, filters ...types.Filter) error {
|
|
||||||
for _, f := range filters {
|
|
||||||
cl.log.With("url", f.URL, "whitelist", whitelist, "enabled", f.Enabled).Info("Add filter")
|
|
||||||
ff := &types.Filter{Name: f.Name, URL: f.URL, Whitelist: whitelist}
|
|
||||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(ff), "/filtering/add_url")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) DeleteFilters(whitelist bool, filters ...types.Filter) error {
|
|
||||||
for _, f := range filters {
|
|
||||||
cl.log.With("url", f.URL, "whitelist", whitelist, "enabled", f.Enabled).Info("Delete filter")
|
|
||||||
ff := &types.Filter{URL: f.URL, Whitelist: whitelist}
|
|
||||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(ff), "/filtering/remove_url")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) UpdateFilters(whitelist bool, filters ...types.Filter) error {
|
|
||||||
for _, f := range filters {
|
|
||||||
cl.log.With("url", f.URL, "whitelist", whitelist, "enabled", f.Enabled).Info("Update filter")
|
|
||||||
fu := &types.FilterUpdate{Whitelist: whitelist, URL: f.URL, Data: types.Filter{ID: f.ID, Name: f.Name, URL: f.URL, Whitelist: whitelist, Enabled: f.Enabled}}
|
|
||||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(fu), "/filtering/set_url")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) RefreshFilters(whitelist bool) error {
|
|
||||||
cl.log.With("whitelist", whitelist).Info("Refresh filter")
|
|
||||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.RefreshFilter{Whitelist: whitelist}), "/filtering/refresh")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) ToggleProtection(enable bool) error {
|
|
||||||
cl.log.With("enable", enable).Info("Toggle protection")
|
|
||||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.Protection{ProtectionEnabled: enable}), "/dns_config")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) SetCustomRules(rules types.UserRules) error {
|
|
||||||
cl.log.With("rules", len(rules)).Info("Set user rules")
|
|
||||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(rules.String()), "/filtering/set_rules")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) ToggleFiltering(enabled bool, interval int) error {
|
|
||||||
cl.log.With("enabled", enabled, "interval", interval).Info("Toggle filtering")
|
|
||||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.FilteringConfig{
|
|
||||||
EnableConfig: types.EnableConfig{Enabled: enabled},
|
|
||||||
IntervalConfig: types.IntervalConfig{Interval: interval},
|
|
||||||
}), "/filtering/config")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) Services() (types.Services, error) {
|
|
||||||
svcs := types.Services{}
|
|
||||||
err := cl.doGet(cl.client.R().EnableTrace().SetResult(&svcs), "/blocked_services/list")
|
|
||||||
return svcs, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) SetServices(services types.Services) error {
|
|
||||||
cl.log.With("services", len(services)).Info("Set services")
|
|
||||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(&services), "/blocked_services/set")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) Clients() (*types.Clients, error) {
|
|
||||||
clients := &types.Clients{}
|
|
||||||
err := cl.doGet(cl.client.R().EnableTrace().SetResult(clients), "/clients")
|
|
||||||
return clients, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) AddClients(clients ...types.Client) error {
|
|
||||||
for _, client := range clients {
|
|
||||||
cl.log.With("name", client.Name).Info("Add client")
|
|
||||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&client), "/clients/add")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) UpdateClients(clients ...types.Client) error {
|
|
||||||
for _, client := range clients {
|
|
||||||
cl.log.With("name", client.Name).Info("Update client")
|
|
||||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&types.ClientUpdate{Name: client.Name, Data: client}), "/clients/update")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) DeleteClients(clients ...types.Client) error {
|
|
||||||
for _, client := range clients {
|
|
||||||
cl.log.With("name", client.Name).Info("Delete client")
|
|
||||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&client), "/clients/delete")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) QueryLogConfig() (*types.QueryLogConfig, error) {
|
|
||||||
qlc := &types.QueryLogConfig{}
|
|
||||||
err := cl.doGet(cl.client.R().EnableTrace().SetResult(qlc), "/querylog_info")
|
|
||||||
return qlc, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) SetQueryLogConfig(enabled bool, interval int, anonymizeClientIP bool) error {
|
|
||||||
cl.log.With("enabled", enabled, "interval", interval, "anonymizeClientIP", anonymizeClientIP).Info("Set query log config")
|
|
||||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.QueryLogConfig{
|
|
||||||
EnableConfig: types.EnableConfig{Enabled: enabled},
|
|
||||||
IntervalConfig: types.IntervalConfig{Interval: interval},
|
|
||||||
AnonymizeClientIP: anonymizeClientIP,
|
|
||||||
}), "/querylog_config")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) StatsConfig() (*types.IntervalConfig, error) {
|
|
||||||
stats := &types.IntervalConfig{}
|
|
||||||
err := cl.doGet(cl.client.R().EnableTrace().SetResult(stats), "/stats_info")
|
|
||||||
return stats, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) SetStatsConfig(interval int) error {
|
|
||||||
cl.log.With("interval", interval).Info("Set stats config")
|
|
||||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.IntervalConfig{Interval: interval}), "/stats_config")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) Setup() error {
|
|
||||||
cl.log.Info("Setup new AdguardHome instance")
|
|
||||||
cfg := &types.InstallConfig{
|
|
||||||
Web: types.InstallPort{
|
|
||||||
IP: "0.0.0.0",
|
|
||||||
Port: 3000,
|
|
||||||
Status: "",
|
|
||||||
CanAutofix: false,
|
|
||||||
},
|
|
||||||
DNS: types.InstallPort{
|
|
||||||
IP: "0.0.0.0",
|
|
||||||
Port: 53,
|
|
||||||
Status: "",
|
|
||||||
CanAutofix: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if cl.client.UserInfo != nil {
|
|
||||||
cfg.Username = cl.client.UserInfo.Username
|
|
||||||
cfg.Password = cl.client.UserInfo.Password
|
|
||||||
}
|
|
||||||
req := cl.client.R().EnableTrace().SetBody(cfg)
|
|
||||||
req.UserInfo = nil
|
|
||||||
return cl.doPost(req, "/install/configure")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) AccessList() (*types.AccessList, error) {
|
|
||||||
al := &types.AccessList{}
|
|
||||||
err := cl.doGet(cl.client.R().EnableTrace().SetResult(al), "/access/list")
|
|
||||||
return al, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) SetAccessList(list *types.AccessList) error {
|
|
||||||
cl.log.Info("Set access list")
|
|
||||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(list), "/access/set")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) DNSConfig() (*types.DNSConfig, error) {
|
|
||||||
cfg := &types.DNSConfig{}
|
|
||||||
err := cl.doGet(cl.client.R().EnableTrace().SetResult(cfg), "/dns_info")
|
|
||||||
return cfg, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) SetDNSConfig(config *types.DNSConfig) error {
|
|
||||||
cl.log.Info("Set dns config list")
|
|
||||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(config), "/dns_config")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) DHCPServerConfig() (*types.DHCPServerConfig, error) {
|
|
||||||
cfg := &types.DHCPServerConfig{}
|
|
||||||
err := cl.doGet(cl.client.R().EnableTrace().SetResult(cfg), "/dhcp/status")
|
|
||||||
return cfg, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) SetDHCPServerConfig(config *types.DHCPServerConfig) error {
|
|
||||||
cl.log.Info("Set dhcp server config")
|
|
||||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(config), "/dhcp/set_config")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) AddDHCPStaticLeases(leases ...types.Lease) error {
|
|
||||||
for _, l := range leases {
|
|
||||||
cl.log.With("mac", l.HWAddr, "ip", l.IP, "hostname", l.Hostname).Info("Add static dhcp lease")
|
|
||||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(l), "/dhcp/add_static_lease")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) DeleteDHCPStaticLeases(leases ...types.Lease) error {
|
|
||||||
for _, l := range leases {
|
|
||||||
cl.log.With("mac", l.HWAddr, "ip", l.IP, "hostname", l.Hostname).Info("Delete static dhcp lease")
|
|
||||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(l), "/dhcp/remove_static_lease")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,623 +0,0 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
|
||||||
// Source: github.com/bakito/adguardhome-sync/pkg/client (interfaces: Client)
|
|
||||||
|
|
||||||
// Package mock_client is a generated GoMock package.
|
|
||||||
package mock_client
|
|
||||||
|
|
||||||
import (
|
|
||||||
reflect "reflect"
|
|
||||||
|
|
||||||
types "github.com/bakito/adguardhome-sync/pkg/types"
|
|
||||||
gomock "github.com/golang/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockClient is a mock of Client interface.
|
|
||||||
type MockClient struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockClientMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockClientMockRecorder is the mock recorder for MockClient.
|
|
||||||
type MockClientMockRecorder struct {
|
|
||||||
mock *MockClient
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockClient creates a new mock instance.
|
|
||||||
func NewMockClient(ctrl *gomock.Controller) *MockClient {
|
|
||||||
mock := &MockClient{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockClientMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockClient) EXPECT() *MockClientMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// AccessList mocks base method.
|
|
||||||
func (m *MockClient) AccessList() (*types.AccessList, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "AccessList")
|
|
||||||
ret0, _ := ret[0].(*types.AccessList)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// AccessList indicates an expected call of AccessList.
|
|
||||||
func (mr *MockClientMockRecorder) AccessList() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AccessList", reflect.TypeOf((*MockClient)(nil).AccessList))
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddClients mocks base method.
|
|
||||||
func (m *MockClient) AddClients(arg0 ...types.Client) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []interface{}{}
|
|
||||||
for _, a := range arg0 {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "AddClients", varargs...)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddClients indicates an expected call of AddClients.
|
|
||||||
func (mr *MockClientMockRecorder) AddClients(arg0 ...interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddClients", reflect.TypeOf((*MockClient)(nil).AddClients), arg0...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddDHCPStaticLeases mocks base method.
|
|
||||||
func (m *MockClient) AddDHCPStaticLeases(arg0 ...types.Lease) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []interface{}{}
|
|
||||||
for _, a := range arg0 {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "AddDHCPStaticLeases", varargs...)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddDHCPStaticLeases indicates an expected call of AddDHCPStaticLeases.
|
|
||||||
func (mr *MockClientMockRecorder) AddDHCPStaticLeases(arg0 ...interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddDHCPStaticLeases", reflect.TypeOf((*MockClient)(nil).AddDHCPStaticLeases), arg0...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddFilters mocks base method.
|
|
||||||
func (m *MockClient) AddFilters(arg0 bool, arg1 ...types.Filter) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []interface{}{arg0}
|
|
||||||
for _, a := range arg1 {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "AddFilters", varargs...)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddFilters indicates an expected call of AddFilters.
|
|
||||||
func (mr *MockClientMockRecorder) AddFilters(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]interface{}{arg0}, arg1...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddFilters", reflect.TypeOf((*MockClient)(nil).AddFilters), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddRewriteEntries mocks base method.
|
|
||||||
func (m *MockClient) AddRewriteEntries(arg0 ...types.RewriteEntry) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []interface{}{}
|
|
||||||
for _, a := range arg0 {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "AddRewriteEntries", varargs...)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddRewriteEntries indicates an expected call of AddRewriteEntries.
|
|
||||||
func (mr *MockClientMockRecorder) AddRewriteEntries(arg0 ...interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRewriteEntries", reflect.TypeOf((*MockClient)(nil).AddRewriteEntries), arg0...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clients mocks base method.
|
|
||||||
func (m *MockClient) Clients() (*types.Clients, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Clients")
|
|
||||||
ret0, _ := ret[0].(*types.Clients)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clients indicates an expected call of Clients.
|
|
||||||
func (mr *MockClientMockRecorder) Clients() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clients", reflect.TypeOf((*MockClient)(nil).Clients))
|
|
||||||
}
|
|
||||||
|
|
||||||
// DHCPServerConfig mocks base method.
|
|
||||||
func (m *MockClient) DHCPServerConfig() (*types.DHCPServerConfig, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "DHCPServerConfig")
|
|
||||||
ret0, _ := ret[0].(*types.DHCPServerConfig)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// DHCPServerConfig indicates an expected call of DHCPServerConfig.
|
|
||||||
func (mr *MockClientMockRecorder) DHCPServerConfig() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DHCPServerConfig", reflect.TypeOf((*MockClient)(nil).DHCPServerConfig))
|
|
||||||
}
|
|
||||||
|
|
||||||
// DNSConfig mocks base method.
|
|
||||||
func (m *MockClient) DNSConfig() (*types.DNSConfig, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "DNSConfig")
|
|
||||||
ret0, _ := ret[0].(*types.DNSConfig)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// DNSConfig indicates an expected call of DNSConfig.
|
|
||||||
func (mr *MockClientMockRecorder) DNSConfig() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DNSConfig", reflect.TypeOf((*MockClient)(nil).DNSConfig))
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteClients mocks base method.
|
|
||||||
func (m *MockClient) DeleteClients(arg0 ...types.Client) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []interface{}{}
|
|
||||||
for _, a := range arg0 {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "DeleteClients", varargs...)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteClients indicates an expected call of DeleteClients.
|
|
||||||
func (mr *MockClientMockRecorder) DeleteClients(arg0 ...interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteClients", reflect.TypeOf((*MockClient)(nil).DeleteClients), arg0...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteDHCPStaticLeases mocks base method.
|
|
||||||
func (m *MockClient) DeleteDHCPStaticLeases(arg0 ...types.Lease) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []interface{}{}
|
|
||||||
for _, a := range arg0 {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "DeleteDHCPStaticLeases", varargs...)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteDHCPStaticLeases indicates an expected call of DeleteDHCPStaticLeases.
|
|
||||||
func (mr *MockClientMockRecorder) DeleteDHCPStaticLeases(arg0 ...interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDHCPStaticLeases", reflect.TypeOf((*MockClient)(nil).DeleteDHCPStaticLeases), arg0...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteFilters mocks base method.
|
|
||||||
func (m *MockClient) DeleteFilters(arg0 bool, arg1 ...types.Filter) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []interface{}{arg0}
|
|
||||||
for _, a := range arg1 {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "DeleteFilters", varargs...)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteFilters indicates an expected call of DeleteFilters.
|
|
||||||
func (mr *MockClientMockRecorder) DeleteFilters(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]interface{}{arg0}, arg1...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteFilters", reflect.TypeOf((*MockClient)(nil).DeleteFilters), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteRewriteEntries mocks base method.
|
|
||||||
func (m *MockClient) DeleteRewriteEntries(arg0 ...types.RewriteEntry) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []interface{}{}
|
|
||||||
for _, a := range arg0 {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "DeleteRewriteEntries", varargs...)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteRewriteEntries indicates an expected call of DeleteRewriteEntries.
|
|
||||||
func (mr *MockClientMockRecorder) DeleteRewriteEntries(arg0 ...interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRewriteEntries", reflect.TypeOf((*MockClient)(nil).DeleteRewriteEntries), arg0...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filtering mocks base method.
|
|
||||||
func (m *MockClient) Filtering() (*types.FilteringStatus, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Filtering")
|
|
||||||
ret0, _ := ret[0].(*types.FilteringStatus)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filtering indicates an expected call of Filtering.
|
|
||||||
func (mr *MockClientMockRecorder) Filtering() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Filtering", reflect.TypeOf((*MockClient)(nil).Filtering))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Host mocks base method.
|
|
||||||
func (m *MockClient) Host() string {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Host")
|
|
||||||
ret0, _ := ret[0].(string)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Host indicates an expected call of Host.
|
|
||||||
func (mr *MockClientMockRecorder) Host() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Host", reflect.TypeOf((*MockClient)(nil).Host))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parental mocks base method.
|
|
||||||
func (m *MockClient) Parental() (bool, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Parental")
|
|
||||||
ret0, _ := ret[0].(bool)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parental indicates an expected call of Parental.
|
|
||||||
func (mr *MockClientMockRecorder) Parental() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Parental", reflect.TypeOf((*MockClient)(nil).Parental))
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryLogConfig mocks base method.
|
|
||||||
func (m *MockClient) QueryLogConfig() (*types.QueryLogConfig, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "QueryLogConfig")
|
|
||||||
ret0, _ := ret[0].(*types.QueryLogConfig)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryLogConfig indicates an expected call of QueryLogConfig.
|
|
||||||
func (mr *MockClientMockRecorder) QueryLogConfig() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryLogConfig", reflect.TypeOf((*MockClient)(nil).QueryLogConfig))
|
|
||||||
}
|
|
||||||
|
|
||||||
// RefreshFilters mocks base method.
|
|
||||||
func (m *MockClient) RefreshFilters(arg0 bool) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "RefreshFilters", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// RefreshFilters indicates an expected call of RefreshFilters.
|
|
||||||
func (mr *MockClientMockRecorder) RefreshFilters(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RefreshFilters", reflect.TypeOf((*MockClient)(nil).RefreshFilters), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RewriteList mocks base method.
|
|
||||||
func (m *MockClient) RewriteList() (*types.RewriteEntries, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "RewriteList")
|
|
||||||
ret0, _ := ret[0].(*types.RewriteEntries)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// RewriteList indicates an expected call of RewriteList.
|
|
||||||
func (mr *MockClientMockRecorder) RewriteList() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RewriteList", reflect.TypeOf((*MockClient)(nil).RewriteList))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SafeBrowsing mocks base method.
|
|
||||||
func (m *MockClient) SafeBrowsing() (bool, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "SafeBrowsing")
|
|
||||||
ret0, _ := ret[0].(bool)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// SafeBrowsing indicates an expected call of SafeBrowsing.
|
|
||||||
func (mr *MockClientMockRecorder) SafeBrowsing() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SafeBrowsing", reflect.TypeOf((*MockClient)(nil).SafeBrowsing))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SafeSearch mocks base method.
|
|
||||||
func (m *MockClient) SafeSearch() (bool, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "SafeSearch")
|
|
||||||
ret0, _ := ret[0].(bool)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// SafeSearch indicates an expected call of SafeSearch.
|
|
||||||
func (mr *MockClientMockRecorder) SafeSearch() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SafeSearch", reflect.TypeOf((*MockClient)(nil).SafeSearch))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Services mocks base method.
|
|
||||||
func (m *MockClient) Services() (types.Services, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Services")
|
|
||||||
ret0, _ := ret[0].(types.Services)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Services indicates an expected call of Services.
|
|
||||||
func (mr *MockClientMockRecorder) Services() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Services", reflect.TypeOf((*MockClient)(nil).Services))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetAccessList mocks base method.
|
|
||||||
func (m *MockClient) SetAccessList(arg0 *types.AccessList) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "SetAccessList", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetAccessList indicates an expected call of SetAccessList.
|
|
||||||
func (mr *MockClientMockRecorder) SetAccessList(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAccessList", reflect.TypeOf((*MockClient)(nil).SetAccessList), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCustomRules mocks base method.
|
|
||||||
func (m *MockClient) SetCustomRules(arg0 types.UserRules) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "SetCustomRules", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCustomRules indicates an expected call of SetCustomRules.
|
|
||||||
func (mr *MockClientMockRecorder) SetCustomRules(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCustomRules", reflect.TypeOf((*MockClient)(nil).SetCustomRules), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDHCPServerConfig mocks base method.
|
|
||||||
func (m *MockClient) SetDHCPServerConfig(arg0 *types.DHCPServerConfig) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "SetDHCPServerConfig", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDHCPServerConfig indicates an expected call of SetDHCPServerConfig.
|
|
||||||
func (mr *MockClientMockRecorder) SetDHCPServerConfig(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDHCPServerConfig", reflect.TypeOf((*MockClient)(nil).SetDHCPServerConfig), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDNSConfig mocks base method.
|
|
||||||
func (m *MockClient) SetDNSConfig(arg0 *types.DNSConfig) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "SetDNSConfig", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDNSConfig indicates an expected call of SetDNSConfig.
|
|
||||||
func (mr *MockClientMockRecorder) SetDNSConfig(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDNSConfig", reflect.TypeOf((*MockClient)(nil).SetDNSConfig), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetQueryLogConfig mocks base method.
|
|
||||||
func (m *MockClient) SetQueryLogConfig(arg0 bool, arg1 int, arg2 bool) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "SetQueryLogConfig", arg0, arg1, arg2)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetQueryLogConfig indicates an expected call of SetQueryLogConfig.
|
|
||||||
func (mr *MockClientMockRecorder) SetQueryLogConfig(arg0, arg1, arg2 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetQueryLogConfig", reflect.TypeOf((*MockClient)(nil).SetQueryLogConfig), arg0, arg1, arg2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetServices mocks base method.
|
|
||||||
func (m *MockClient) SetServices(arg0 types.Services) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "SetServices", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetServices indicates an expected call of SetServices.
|
|
||||||
func (mr *MockClientMockRecorder) SetServices(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetServices", reflect.TypeOf((*MockClient)(nil).SetServices), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetStatsConfig mocks base method.
|
|
||||||
func (m *MockClient) SetStatsConfig(arg0 int) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "SetStatsConfig", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetStatsConfig indicates an expected call of SetStatsConfig.
|
|
||||||
func (mr *MockClientMockRecorder) SetStatsConfig(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetStatsConfig", reflect.TypeOf((*MockClient)(nil).SetStatsConfig), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup mocks base method.
|
|
||||||
func (m *MockClient) Setup() error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Setup")
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup indicates an expected call of Setup.
|
|
||||||
func (mr *MockClientMockRecorder) Setup() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Setup", reflect.TypeOf((*MockClient)(nil).Setup))
|
|
||||||
}
|
|
||||||
|
|
||||||
// StatsConfig mocks base method.
|
|
||||||
func (m *MockClient) StatsConfig() (*types.IntervalConfig, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "StatsConfig")
|
|
||||||
ret0, _ := ret[0].(*types.IntervalConfig)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// StatsConfig indicates an expected call of StatsConfig.
|
|
||||||
func (mr *MockClientMockRecorder) StatsConfig() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StatsConfig", reflect.TypeOf((*MockClient)(nil).StatsConfig))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status mocks base method.
|
|
||||||
func (m *MockClient) Status() (*types.Status, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Status")
|
|
||||||
ret0, _ := ret[0].(*types.Status)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status indicates an expected call of Status.
|
|
||||||
func (mr *MockClientMockRecorder) Status() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockClient)(nil).Status))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToggleFiltering mocks base method.
|
|
||||||
func (m *MockClient) ToggleFiltering(arg0 bool, arg1 int) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "ToggleFiltering", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToggleFiltering indicates an expected call of ToggleFiltering.
|
|
||||||
func (mr *MockClientMockRecorder) ToggleFiltering(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleFiltering", reflect.TypeOf((*MockClient)(nil).ToggleFiltering), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToggleParental mocks base method.
|
|
||||||
func (m *MockClient) ToggleParental(arg0 bool) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "ToggleParental", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToggleParental indicates an expected call of ToggleParental.
|
|
||||||
func (mr *MockClientMockRecorder) ToggleParental(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleParental", reflect.TypeOf((*MockClient)(nil).ToggleParental), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToggleProtection mocks base method.
|
|
||||||
func (m *MockClient) ToggleProtection(arg0 bool) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "ToggleProtection", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToggleProtection indicates an expected call of ToggleProtection.
|
|
||||||
func (mr *MockClientMockRecorder) ToggleProtection(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleProtection", reflect.TypeOf((*MockClient)(nil).ToggleProtection), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToggleSafeBrowsing mocks base method.
|
|
||||||
func (m *MockClient) ToggleSafeBrowsing(arg0 bool) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "ToggleSafeBrowsing", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToggleSafeBrowsing indicates an expected call of ToggleSafeBrowsing.
|
|
||||||
func (mr *MockClientMockRecorder) ToggleSafeBrowsing(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleSafeBrowsing", reflect.TypeOf((*MockClient)(nil).ToggleSafeBrowsing), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToggleSafeSearch mocks base method.
|
|
||||||
func (m *MockClient) ToggleSafeSearch(arg0 bool) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "ToggleSafeSearch", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToggleSafeSearch indicates an expected call of ToggleSafeSearch.
|
|
||||||
func (mr *MockClientMockRecorder) ToggleSafeSearch(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleSafeSearch", reflect.TypeOf((*MockClient)(nil).ToggleSafeSearch), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateClients mocks base method.
|
|
||||||
func (m *MockClient) UpdateClients(arg0 ...types.Client) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []interface{}{}
|
|
||||||
for _, a := range arg0 {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "UpdateClients", varargs...)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateClients indicates an expected call of UpdateClients.
|
|
||||||
func (mr *MockClientMockRecorder) UpdateClients(arg0 ...interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateClients", reflect.TypeOf((*MockClient)(nil).UpdateClients), arg0...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateFilters mocks base method.
|
|
||||||
func (m *MockClient) UpdateFilters(arg0 bool, arg1 ...types.Filter) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []interface{}{arg0}
|
|
||||||
for _, a := range arg1 {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "UpdateFilters", varargs...)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateFilters indicates an expected call of UpdateFilters.
|
|
||||||
func (mr *MockClientMockRecorder) UpdateFilters(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]interface{}{arg0}, arg1...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateFilters", reflect.TypeOf((*MockClient)(nil).UpdateFilters), varargs...)
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user