mirror of https://github.com/etcd-io/etcd.git
Compare commits
875 Commits
pkg/v3.6.2
...
main
Author | SHA1 | Date |
---|---|---|
|
f84fb66cbe | |
|
8ff509c137 | |
|
24b5ac7f90 | |
|
045930b783 | |
|
4c46235bb8 | |
|
bf5f9af3ee | |
|
a291faad71 | |
|
fb303a8360 | |
|
a8a97a5d9c | |
|
b8693fb17a | |
|
5af30121bf | |
|
87172a157e | |
|
40f4ea8c48 | |
|
e53bad4fdc | |
|
625333c8bd | |
|
233021b2d7 | |
|
5dde6104e0 | |
|
2f5a091512 | |
|
e8090a2c73 | |
|
2356ac2b8b | |
|
4993175bf2 | |
|
4e3e13eac9 | |
|
798c983dd8 | |
|
ea627c79e2 | |
|
a110ee35b8 | |
|
e3e5a84307 | |
|
02a4c53e59 | |
|
033b9e270d | |
|
3fbf1bbf51 | |
|
61c62abc54 | |
|
075509c848 | |
|
a93551f8b2 | |
|
587388fb3d | |
|
0d66443de9 | |
|
85b7dd4693 | |
|
9671fb369c | |
|
dc7d95e243 | |
|
b883f0dd11 | |
|
f1a6777ec1 | |
|
fe91daaf83 | |
|
e4176d9859 | |
|
f10ebf6f9d | |
|
26c08aab1f | |
|
345f4f895c | |
|
aea7741e19 | |
|
9fb8c3685f | |
|
cf0369e4ad | |
|
44a094a20a | |
|
866bc0717c | |
|
2c311cc948 | |
|
4b9846924e | |
|
a15cd5e0ac | |
|
721ba5bc24 | |
|
2e242a63fb | |
|
046b064728 | |
|
e4bf5e0ba7 | |
|
df8d67ae82 | |
|
361312435e | |
|
763a5fd3c1 | |
|
7a79b05f7d | |
|
9092857864 | |
|
6792656b74 | |
|
eabcedf785 | |
|
39fb26d031 | |
|
3ea0a5e4ff | |
|
0199ccabe0 | |
|
36d8f5465f | |
|
d5537ec250 | |
|
674260e85d | |
|
b51415c3f6 | |
|
b42b5f9d32 | |
|
ab2cb8dcdb | |
|
c9e373db43 | |
|
b013138901 | |
|
d80924d3af | |
|
3773a3b064 | |
|
d744c19762 | |
|
ee6d86f9b3 | |
|
762582c48c | |
|
e08120e696 | |
|
015d7c5704 | |
|
1a2efc3c47 | |
|
973847aa24 | |
|
bc47e77116 | |
|
795cca9bc3 | |
|
084394a17a | |
|
a8c4903b70 | |
|
b3b0fdc513 | |
|
dc7fe2a94e | |
|
9e4c432bb0 | |
|
fd8d737838 | |
|
6f8ef3cc30 | |
|
c6804c8d8c | |
|
cc240d6c08 | |
|
955f94cf65 | |
|
500642c0b6 | |
|
0458e119a7 | |
|
fb7e3f2460 | |
|
3dfa3e3b5a | |
|
33a458068d | |
|
32eeee612e | |
|
ae8a57fb76 | |
|
511f795e1b | |
|
4cd8c08e6e | |
|
87a2ed37ab | |
|
2129bbf9e9 | |
|
f4ecf54482 | |
|
eab99c53bf | |
|
86fa2e0991 | |
|
be6e482948 | |
|
745f31c12d | |
|
5dfd6e05a4 | |
|
d4a1798487 | |
|
b64a02a5a5 | |
|
52ce705dc7 | |
|
fba53f1034 | |
|
88d7e777f8 | |
|
e6be3f3723 | |
|
c68e3f4275 | |
|
db4dfbde2b | |
|
d61b803d3b | |
|
4f2ede9638 | |
|
d37ff81bbb | |
|
b6a4e35319 | |
|
9bc99b1634 | |
|
cc29bc0391 | |
|
2e3c87e413 | |
|
dcaddc60c2 | |
|
6aaf32b661 | |
|
f6d6111eca | |
|
3303c356d0 | |
|
8142396ae8 | |
|
40393403a5 | |
|
abf23ea86c | |
|
b11ab1d0cb | |
|
9f5ba66961 | |
|
1e357cfc90 | |
|
1432e63f49 | |
|
bfbf25ca04 | |
|
91d931ad08 | |
|
989dd8e9a9 | |
|
6d60b4c850 | |
|
7847848865 | |
|
a091a6d716 | |
|
85191bd75b | |
|
dc80204eef | |
|
5074f65cb6 | |
|
04e31213b4 | |
|
780aef5f6e | |
|
57c01974ea | |
|
843658f16c | |
|
707bc6fbc0 | |
|
7dd65ecd1c | |
|
3154df0d4d | |
|
b8d1accc7d | |
|
2e455e5680 | |
|
58d65781e8 | |
|
84caef6ac8 | |
|
c58c0bd1c6 | |
|
50e0672184 | |
|
87d3908208 | |
|
df4a65acb7 | |
|
bbe99cecb6 | |
|
3be967f201 | |
|
37209aed8b | |
|
5b11ed4622 | |
|
1934e1126c | |
|
58dadb1f1d | |
|
ea5b6030a6 | |
|
fa7b780498 | |
|
d4f85e58a3 | |
|
93d4e087c7 | |
|
10fbccbd39 | |
|
a756eaf122 | |
|
ab267ab803 | |
|
47ac8bdf22 | |
|
3f9f0745ca | |
|
abab2cdfa8 | |
|
02b4add11d | |
|
d8c3007ca6 | |
|
f89b3ae183 | |
|
66ba3990ab | |
|
4d32b9ab73 | |
|
7e467f7e19 | |
|
2553e66210 | |
|
154995c5ad | |
|
32c7792f48 | |
|
1bd07cd2af | |
|
2e5a835689 | |
|
54ddc02b94 | |
|
ba585ea26c | |
|
22f8d56ca7 | |
|
7c651c43c2 | |
|
a6c57a791b | |
|
90f398fe00 | |
|
0c19ee442c | |
|
f989f2361c | |
|
1fbf7a77c0 | |
|
029f02cf0d | |
|
2aa01b6cc3 | |
|
ba8f9fffb9 | |
|
97b2845f37 | |
|
be1e58af99 | |
|
fe1393cac0 | |
|
6651d64e22 | |
|
9707af3b23 | |
|
142cdbfc5f | |
|
d5b71e7eeb | |
|
fb5b53495b | |
|
7a576bfaab | |
|
d465a15dc3 | |
|
03c0f758ce | |
|
de65862cc2 | |
|
b431e05e5e | |
|
98ab934211 | |
|
3d3fcc6942 | |
|
41d6379e84 | |
|
6bbbca8a21 | |
|
09fb9576cc | |
|
bbb2db6435 | |
|
e97ebb9364 | |
|
b0f4b1c737 | |
|
84ed1cd100 | |
|
edb9abda4c | |
|
9b09800633 | |
|
e6769d7724 | |
|
2d10b4cbb6 | |
|
f23c9d7fd3 | |
|
193189637e | |
|
c272adec29 | |
|
ec416efcc2 | |
|
ecfea37975 | |
|
f598a198e9 | |
|
b6167b7c5d | |
|
b6616c25dc | |
|
b2008781b6 | |
|
020077d520 | |
|
61126b9fd1 | |
|
b5e397922b | |
|
28d6ed75bc | |
|
0a93a28e7a | |
|
76d18dc936 | |
|
c904edc174 | |
|
3f8df6d6b1 | |
|
5e4ae51ed4 | |
|
b039448e2f | |
|
f1a4f10324 | |
|
929ac02dbe | |
|
bfd476f7c3 | |
|
18d30da95c | |
|
74fa9d7d94 | |
|
90722adaa1 | |
|
c84b416f84 | |
|
69f0771c87 | |
|
d4dda0a1c8 | |
|
b3b9e986e0 | |
|
3057ae1160 | |
|
45f4ab7953 | |
|
918e182209 | |
|
0fb9499f8b | |
|
0e40709eac | |
|
ae01ad849a | |
|
c36d8a7d78 | |
|
d8ca9eb39d | |
|
7aa8288130 | |
|
6115db53a8 | |
|
d9651182f7 | |
|
d9f4f8118c | |
|
14e33a817f | |
|
629741db60 | |
|
b7db5568f4 | |
|
9349b23f0f | |
|
225269df69 | |
|
fb5bc004e7 | |
|
823556cfe4 | |
|
96e40b27d2 | |
|
61375682c2 | |
|
7728d8fc8e | |
|
15612bd968 | |
|
ebcfdd0185 | |
|
3a3962442e | |
|
457c2186a6 | |
|
2a225549c8 | |
|
c604bfdf44 | |
|
622a0100d8 | |
|
8859f41bf7 | |
|
c0132db604 | |
|
6e2be32266 | |
|
256bfd25d4 | |
|
6c807b30f2 | |
|
e7b3aa738f | |
|
55ee50089f | |
|
1f7fdc39a6 | |
|
c4f3290322 | |
|
e2fcdeccdb | |
|
0167cb3075 | |
|
c849507a38 | |
|
ed4b331c62 | |
|
27bfe80df3 | |
|
4312b20009 | |
|
0287b75950 | |
|
a119975319 | |
|
a4dc023d65 | |
|
2ba6ba7dcc | |
|
be2c3ca801 | |
|
4119b58e69 | |
|
558e48f155 | |
|
fc0e79492c | |
|
ce21dd5db2 | |
|
882713c4b6 | |
|
4f45c346ba | |
|
f2f647682f | |
|
56254468f2 | |
|
73fdbfe848 | |
|
f19aca6858 | |
|
0b68c64527 | |
|
4800faaf70 | |
|
c675eef4c7 | |
|
a029b090cb | |
|
d68eac5a37 | |
|
d545dfd4e6 | |
|
9410e64a42 | |
|
dbf884e422 | |
|
32c4fbfba7 | |
|
216328bb31 | |
|
4c65b6eb41 | |
|
d1bf06c60d | |
|
47c840533d | |
|
2e99e46f5a | |
|
cb40b4e226 | |
|
be8f09a9dc | |
|
19908558c9 | |
|
1280c5bf73 | |
|
d3478bfcc6 | |
|
afbb3b437c | |
|
e5572b2b6f | |
|
14448c52f5 | |
|
cdc0f9e6d2 | |
|
592c195ae2 | |
|
c47a2916be | |
|
e7dd2aca55 | |
|
08864da0ab | |
|
c6dd1c552f | |
|
9564549df4 | |
|
4ff8b06dab | |
|
8a64f7a810 | |
|
a7d1fd5ca8 | |
|
ff6d645779 | |
|
a460abc7f9 | |
|
61489056d5 | |
|
690f315b9e | |
|
fd0599461e | |
|
d564fb9699 | |
|
4a611b631d | |
|
d40a5946d1 | |
|
a426b0fc9a | |
|
b90252f859 | |
|
b605213c05 | |
|
68230c1482 | |
|
750618c2e3 | |
|
f206a8a579 | |
|
7770640d66 | |
|
a54e7e00c8 | |
|
6becfe6461 | |
|
5e9a6ef1ad | |
|
452f0a1f10 | |
|
ecd8a37e81 | |
|
67dae5b26e | |
|
73f440bf3f | |
|
8f155a3999 | |
|
a87600eeb2 | |
|
0c43c4b868 | |
|
4cdcaa069c | |
|
a7d8b942e2 | |
|
7c4e448717 | |
|
41fd9ad055 | |
|
9be7d684a8 | |
|
67738de788 | |
|
094a4adfd6 | |
|
11452157c1 | |
|
f27a4d5b7f | |
|
d1aae3164a | |
|
92092f19a4 | |
|
dcd9f98f5a | |
|
be23337121 | |
|
6d5eb7b5fc | |
|
3546091fdf | |
|
56bc42a65f | |
|
1399c066e0 | |
|
276839a2e4 | |
|
bbba58b64e | |
|
5363cc6a5c | |
|
7a979db6d1 | |
|
102efd7bd6 | |
|
626e638d77 | |
|
bcf4deee68 | |
|
2044e24503 | |
|
47de6a90fc | |
|
ed9aa56f02 | |
|
207d9f7663 | |
|
0b4b31f346 | |
|
567e9a76cb | |
|
9045ef9122 | |
|
7085d3a1b4 | |
|
7b011cbb2f | |
|
ddd5f68b8e | |
|
c34c6b3c0e | |
|
d794d9a54e | |
|
aac5a97aa7 | |
|
26d212dd36 | |
|
9aae00c0fd | |
|
bce900db12 | |
|
8f69168578 | |
|
358d245a45 | |
|
216ce8f465 | |
|
e4ee974bed | |
|
da989fe183 | |
|
270f6a9522 | |
|
24e0999e4f | |
|
9704426df6 | |
|
3fb4ecd0ba | |
|
5a6ffdc0bb | |
|
cd42de79da | |
|
49767381e8 | |
|
be396789db | |
|
03177c1a3a | |
|
b5ef870433 | |
|
4a8827b1e9 | |
|
80b550f42d | |
|
b6de745906 | |
|
fa80bbda68 | |
|
6e0213be06 | |
|
2ff63aa9e5 | |
|
cd2a292431 | |
|
11809a312e | |
|
297e9d1d58 | |
|
a6b9029d4a | |
|
1158c17d4f | |
|
c3f7b11a86 | |
|
dc310f2d32 | |
|
7530a65c31 | |
|
baa7ed90f5 | |
|
9284ac347d | |
|
aedae3a4fa | |
|
d4b56bdebd | |
|
822b5aad78 | |
|
51954f7e85 | |
|
7a58009841 | |
|
e3a2e1f438 | |
|
94c632c464 | |
|
8e28df0496 | |
|
1cc7eedca4 | |
|
6e1d8153ac | |
|
dbb76f3fcd | |
|
b2f243c59b | |
|
efe7f8158c | |
|
24b9a32c03 | |
|
c70cb53fe7 | |
|
ab2248ae25 | |
|
7f8da618e5 | |
|
158b9e0d46 | |
|
994cebcbbd | |
|
3860239a9b | |
|
15850236b3 | |
|
eae9a91a20 | |
|
1ab5ed13a2 | |
|
4ad65d9d7c | |
|
15e8ec7524 | |
|
29419f7c37 | |
|
7a43280da7 | |
|
07c6977011 | |
|
90e17d8b43 | |
|
965603e2de | |
|
c940bf55bf | |
|
d610b0431b | |
|
f5bc7615ed | |
|
b1e76801e7 | |
|
0a0fde11ac | |
|
c590dd6c58 | |
|
a5834f89bb | |
|
28d10d35c0 | |
|
43a43f3229 | |
|
64cd6b643b | |
|
1ba42cedaa | |
|
8da854dc69 | |
|
8f63a8d6d8 | |
|
28546faaab | |
|
e4fb13d93a | |
|
88bba4d86e | |
|
ef211761b3 | |
|
1a3a5a491e | |
|
a28659010f | |
|
881cce9943 | |
|
021aece959 | |
|
d3f49a1334 | |
|
dd14d92af7 | |
|
e80f3258d4 | |
|
e271b78223 | |
|
2310515a91 | |
|
51e38f43eb | |
|
3cf32c009c | |
|
e70740be45 | |
|
8ca65f523d | |
|
4d698b3dd2 | |
|
c3d7b2a316 | |
|
abf043919b | |
|
a4bbb46b86 | |
|
2876a4ece5 | |
|
9d246198b5 | |
|
799f07bfff | |
|
21a976e9e2 | |
|
da2837c54a | |
|
de5e4a5281 | |
|
cfb58eea7d | |
|
cc90010466 | |
|
6608dfb2a5 | |
|
491199f4f0 | |
|
aa8238f511 | |
|
1fa4e01ad6 | |
|
f5dbde1d19 | |
|
4fec4ca20c | |
|
c6ae58ee8d | |
|
0ee26e9438 | |
|
bf27b54f43 | |
|
035631f511 | |
|
b3bcfe802e | |
|
fa38b54dd5 | |
|
f35125b31f | |
|
6b87e2cb68 | |
|
0392f5470d | |
|
8f933a5b58 | |
|
27ff3ecfc3 | |
|
ca818927c2 | |
|
09f897de4d | |
|
028f107327 | |
|
0a9c51210b | |
|
dd23499144 | |
|
7f51b1bf3b | |
|
638b410173 | |
|
818dec3f2f | |
|
21515e1454 | |
|
061e59aef2 | |
|
5ac6079c80 | |
|
aa2a025f7b | |
|
ca6f942723 | |
|
58086dadeb | |
|
916b5a941e | |
|
0b9d021403 | |
|
4585ee9280 | |
|
ff151eea12 | |
|
03839c99c7 | |
|
be71a86f7f | |
|
59c2581a14 | |
|
08c09be0bb | |
|
ddeaba726c | |
|
4591695bf1 | |
|
4443221dc4 | |
|
8318e06fdf | |
|
84d8924822 | |
|
2d4fd6747b | |
|
833b2c4263 | |
|
b90c5bce98 | |
|
d4db4386a8 | |
|
650232cd64 | |
|
d3cebaec93 | |
|
1c77c1480e | |
|
4b7e512a66 | |
|
a730bbda20 | |
|
5a8fba4660 | |
|
86ab134ed3 | |
|
282cc69b2d | |
|
c5a5afda65 | |
|
3f524812e1 | |
|
0cec47ad42 | |
|
0ad7ea497c | |
|
3a2e497d00 | |
|
e9fcb41983 | |
|
898f33ffac | |
|
0ff4a1a2a2 | |
|
8949669ff8 | |
|
b0958f620a | |
|
3084319165 | |
|
25889c08c1 | |
|
4b76a9d4c6 | |
|
928581eae2 | |
|
5ec2b2cbc6 | |
|
3b66c82934 | |
|
fbaf89bfcf | |
|
d4868711e2 | |
|
dbf444ca96 | |
|
ab98cddd30 | |
|
ad13c0dac1 | |
|
d6416b8773 | |
|
03b253714d | |
|
ba600b25c2 | |
|
aa2472e617 | |
|
7351ab86c0 | |
|
df935aa3b4 | |
|
9f6a829815 | |
|
aac61b5f4f | |
|
37eaafa5a7 | |
|
a203755b3c | |
|
839985d96e | |
|
ccec08989f | |
|
eb7662d6cb | |
|
43bbbbfc0b | |
|
cad737b595 | |
|
b8762ac6ed | |
|
6caa1ecd0b | |
|
c983744ac2 | |
|
eaeea10e7f | |
|
022688bc6d | |
|
1cf63f1899 | |
|
0fbac24b29 | |
|
fe44a4bb81 | |
|
c19290acc7 | |
|
375a71a520 | |
|
c065b833f4 | |
|
924a2e68c8 | |
|
afe2641a46 | |
|
02d62f69a3 | |
|
d1f080129f | |
|
2733667894 | |
|
e2b37cfae8 | |
|
c80e61792e | |
|
2514f03944 | |
|
dedbb5160d | |
|
35d56cce0f | |
|
5c7b322817 | |
|
dc56041835 | |
|
dfb9df419c | |
|
3b9ee9bb2c | |
|
5ee55a69e0 | |
|
d6d6769afb | |
|
26d537b33f | |
|
92f8fa367a | |
|
1db0fc5818 | |
|
baeb60ab82 | |
|
473a5ffa05 | |
|
211f9e52cb | |
|
f4a08f77d4 | |
|
6b76999f51 | |
|
9c7ffc32f0 | |
|
fd4fc82d40 | |
|
ee2f4e0e9b | |
|
e55e55b1bb | |
|
a36e5bdfac | |
|
5e272463f7 | |
|
29878cf735 | |
|
dc45df0cad | |
|
fb53c4a376 | |
|
343b14ba06 | |
|
cd077a806d | |
|
53b88df4a7 | |
|
495176a033 | |
|
dd233b46cd | |
|
4ea0bb6e46 | |
|
bb855eb570 | |
|
2e568f6ff6 | |
|
4aee7f109b | |
|
5eac5996f7 | |
|
3a6b064da3 | |
|
040e5ed65c | |
|
b988791c8e | |
|
2555efddbf | |
|
d05b8b7611 | |
|
b692041b69 | |
|
f07e2ae4ed | |
|
49b4137746 | |
|
40f21b203b | |
|
a2eedb9093 | |
|
5122d43d87 | |
|
d100da7431 | |
|
e31514dc84 | |
|
630fb0a43b | |
|
c571cff478 | |
|
be3eee2597 | |
|
82a3e6fe27 | |
|
c2701197fb | |
|
aedecad09f | |
|
5eeacec087 | |
|
41cc8cd3f7 | |
|
a72780654d | |
|
75742cf025 | |
|
395a03843d | |
|
8b4c2cc11e | |
|
ce5620760b | |
|
a5bef1b837 | |
|
d87cb88d57 | |
|
175f18ee8e | |
|
b92073d1cb | |
|
2717859dab | |
|
6c69b1a709 | |
|
dc6db52c34 | |
|
3e207cfda0 | |
|
a3b17f5ba8 | |
|
05d57531e4 | |
|
6984bbe731 | |
|
5eca42b3cf | |
|
f4c40b7fd0 | |
|
3f20b705fe | |
|
619d949b8d | |
|
4804c48d8b | |
|
ffb6ff9acf | |
|
6635bf633e | |
|
3c916bb258 | |
|
dc09500b8f | |
|
d0dda55867 | |
|
06edd83d28 | |
|
f4e1d0e1bc | |
|
d0b8863552 | |
|
85d90d9dd9 | |
|
890990c32c | |
|
22b5b78530 | |
|
9f1709e015 | |
|
8e4de789e5 | |
|
7e66d3a9fc | |
|
930bf4c41b | |
|
5dea90cc95 | |
|
b88df6ea53 | |
|
f0a1ec501b | |
|
7456d52dab | |
|
31650ab0c8 | |
|
028773129a | |
|
b58e9f522f | |
|
316991e8d1 | |
|
31ff0a9cc8 | |
|
b9ceba8c9f | |
|
5da0dc0091 | |
|
04710062d1 | |
|
7c866508ef | |
|
fa3dca3f41 | |
|
a9b24c6523 | |
|
ceb11b7ce9 | |
|
e69f9c78ac | |
|
bf9d6f588c | |
|
fa9233bbbc | |
|
215ab98a5f | |
|
3d381537cc | |
|
d23b0785c1 | |
|
9fcb5f6ae4 | |
|
e2459c0323 | |
|
65691111dc | |
|
2f15f0859f | |
|
4523ff77c9 | |
|
edfb9b7565 | |
|
7c7f69e35a | |
|
25d8e7fb40 | |
|
f9c68849ab | |
|
4ef3b376b7 | |
|
e99f20a639 | |
|
3e43e4594e | |
|
4ce48cea70 | |
|
b16bd864ef | |
|
ed975c2677 | |
|
3a2cece119 | |
|
a6b3c93cb4 | |
|
5c147c0144 | |
|
9b31bdc7a8 | |
|
ac064e860c | |
|
b9298ed950 | |
|
f5409c4227 | |
|
d42b0434a6 | |
|
a423349c07 | |
|
b1ecb206b4 | |
|
49fbd1ef95 | |
|
d76671ca80 | |
|
6ecb6fba34 | |
|
a8d8c1887f | |
|
16b1460b5d | |
|
47e764fd6b | |
|
99683d59ac | |
|
db2213b70a | |
|
66748c0d46 | |
|
3763a67606 | |
|
4a9ab7c380 | |
|
78a7ca618e | |
|
48fcef5b47 | |
|
6784151a1b | |
|
df26009e75 | |
|
5e89197c28 | |
|
f1af8b9e92 | |
|
8e44d3ac65 | |
|
6d66d828b4 | |
|
628435abdb | |
|
c81fb870bb | |
|
498bbc5982 | |
|
43df62f6b9 | |
|
a081904832 | |
|
4cef06bad9 | |
|
d83346bfe7 | |
|
f954f76012 | |
|
022b9b22cf | |
|
d52bd901b4 | |
|
2de17bd396 | |
|
cbc14d78e6 | |
|
ac7d3e9335 | |
|
65159a2b96 | |
|
091b6ed718 | |
|
97c63c9fde | |
|
8ce740cb26 | |
|
1c7f0062fc | |
|
68c2fae46a | |
|
6f86f4fbb0 | |
|
f312736892 | |
|
49f34c9751 | |
|
82c371a471 | |
|
a29b97ab8c | |
|
8575de3bab | |
|
9d57554c4c | |
|
cf6f4fbb94 | |
|
ef65923cf7 | |
|
deb9178089 | |
|
1e3710a90f | |
|
e10f01edfe | |
|
d86775c276 | |
|
e37cdc66c4 | |
|
6f27da2aee | |
|
c0c9f7c344 | |
|
eb7607bd8b | |
|
c86bb08fa9 | |
|
189eb92e3c | |
|
c733872ea3 | |
|
0bbc42a3de | |
|
14cf6694e9 | |
|
5c9db9c840 | |
|
b1e513cfe9 | |
|
8c52b414f3 | |
|
91762ccd4f | |
|
5fa8c1f6b8 | |
|
865ed4a0b6 | |
|
fb2cdc31c7 | |
|
3bd966d6b5 | |
|
de10fd6565 | |
|
075fb1058a | |
|
b77a28cbb5 | |
|
8af64f886c | |
|
9c30af2ff5 | |
|
eea79ca9a5 | |
|
fe81901c74 | |
|
e37615a483 | |
|
7073c4140f | |
|
8b99f8e025 | |
|
dd6791c560 | |
|
1f4b3cdf52 | |
|
7ba8929e7b | |
|
95efe44ae0 | |
|
f30cbaac11 | |
|
57dc6f375a | |
|
c0e7e8c873 | |
|
5414dbab4a | |
|
1709422e21 | |
|
fefce5498f | |
|
0dfc7ab52c | |
|
5703e6b387 | |
|
53d44f0f9c | |
|
5802231328 | |
|
f197f44537 | |
|
b746159a22 | |
|
a9b8cba602 | |
|
1ea4f435cc | |
|
ad3301099a | |
|
fa2926adb0 | |
|
04cce9759b | |
|
b31311ebb0 | |
|
2cffac9916 | |
|
ab88026649 | |
|
2908d45333 | |
|
948a464f16 | |
|
e499f01400 | |
|
097be8cff3 | |
|
38bb52acd3 | |
|
89dd13f93b | |
|
94758c1760 | |
|
6c866548dc |
|
@ -3,7 +3,7 @@
|
|||
{
|
||||
"name": "Go",
|
||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||
"image": "mcr.microsoft.com/devcontainers/go:1.23-bookworm",
|
||||
"image": "mcr.microsoft.com/devcontainers/go:1.24-bookworm",
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
|
||||
|
|
|
@ -10,7 +10,7 @@ body:
|
|||
label: Bug report criteria
|
||||
description: Please confirm this bug report meets the following criteria.
|
||||
options:
|
||||
- label: This bug report is not security related, security issues should be disclosed privately via [etcd maintainers](mailto:etcd-maintainers@googlegroups.com).
|
||||
- label: This bug report is not security related, security issues should be disclosed privately via security@etcd.io.
|
||||
- label: This is not a support request or question, support requests or questions should be raised in the etcd [discussion forums](https://github.com/etcd-io/etcd/discussions).
|
||||
- label: You have read the etcd [bug reporting guidelines](https://github.com/etcd-io/etcd/blob/main/Documentation/contributor-guide/reporting_bugs.md).
|
||||
- label: Existing open issues along with etcd [frequently asked questions](https://etcd.io/docs/latest/faq) have been checked and this is not a duplicate.
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
---
|
||||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
||||
daysUntilStale: 90
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
||||
daysUntilClose: 21
|
||||
|
||||
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
|
||||
onlyLabels: []
|
||||
|
||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
||||
exemptLabels:
|
||||
- "stage/tracked"
|
||||
|
||||
# Set to true to ignore issues in a project (defaults to false)
|
||||
exemptProjects: false
|
||||
|
||||
# Set to true to ignore issues in a milestone (defaults to false)
|
||||
exemptMilestones: false
|
||||
|
||||
# Set to true to ignore issues with an assignee (defaults to false)
|
||||
exemptAssignees: false
|
||||
|
||||
# Label to use when marking as stale
|
||||
staleLabel: stale
|
||||
|
||||
# Comment to post when marking as stale. Set to `false` to disable
|
||||
markComment: This issue has been automatically marked as stale because it has not had recent activity. It will be closed after 21 days if no further activity occurs. Thank you for your contributions.
|
||||
# Comment to post when removing the stale label.
|
||||
# unmarkComment: >
|
||||
# Your comment here.
|
||||
|
||||
# Comment to post when closing a stale Issue or Pull Request.
|
||||
# closeComment: >
|
||||
# Your comment here.
|
||||
|
||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||
limitPerRun: 30
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
# only: issues
|
||||
|
||||
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
|
||||
# pulls:
|
||||
# daysUntilStale: 30
|
||||
# markComment: >
|
||||
# This pull request has been automatically marked as stale because it has not had
|
||||
# recent activity. It will be closed if no further activity occurs. Thank you
|
||||
# for your contributions.
|
||||
|
||||
# issues:
|
||||
# exemptLabels:
|
||||
# - confirmed
|
|
@ -0,0 +1,106 @@
|
|||
---
|
||||
name: Build and trigger Antithesis exploration
|
||||
|
||||
on:
|
||||
# Disabled as discussed in https://github.com/etcd-io/etcd/pull/19750#issuecomment-2809840402
|
||||
# pull_request:
|
||||
# branches: [main]
|
||||
# schedule:
|
||||
# - cron: "0 0 * * *" # run every day at midnight
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
test:
|
||||
description: 'Test name'
|
||||
required: false
|
||||
default: 'etcd nightly antithesis run'
|
||||
type: string
|
||||
duration:
|
||||
description: 'Duration (exploration hours)'
|
||||
required: true
|
||||
type: int
|
||||
default: 12
|
||||
description:
|
||||
description: 'Description (avoid quotes, please!)'
|
||||
required: true
|
||||
type: string
|
||||
default: "etcd nightly antithesis run"
|
||||
etcd_ref:
|
||||
description: 'etcd version to build etcd-server from'
|
||||
required: false
|
||||
type: string
|
||||
default: 'release-3.5'
|
||||
email:
|
||||
description: 'Additional email notification recipient (separate with ;)'
|
||||
required: true
|
||||
type: string
|
||||
default: ""
|
||||
cfg_node_count:
|
||||
description: 'Number of nodes in the etcd cluster'
|
||||
required: false
|
||||
type: int
|
||||
default: 3
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions: read-all
|
||||
|
||||
env:
|
||||
REGISTRY: us-central1-docker.pkg.dev
|
||||
REPOSITORY: molten-verve-216720/linuxfoundation-repository
|
||||
|
||||
jobs:
|
||||
build-and-push-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
environment: Antithesis
|
||||
env:
|
||||
CFG_NODE_COUNT: ${{ inputs.cfg_node_count }}
|
||||
steps:
|
||||
- name: Checkout the code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Login to Antithesis Docker Registry
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: _json_key
|
||||
password: ${{ secrets.ANTITHESIS_CONTAINER_REGISTRY_TOKEN }}
|
||||
|
||||
- name: Build and push config image
|
||||
working-directory: ./tests/antithesis
|
||||
run: |
|
||||
make antithesis-build-config-image IMAGE_TAG=${{ inputs.etcd_ref }}_${{ github.sha }}
|
||||
export IMAGE="${{ env.REGISTRY }}/${{ env.REPOSITORY }}/etcd-config:${{ inputs.etcd_ref }}_${{ github.sha }}"
|
||||
docker tag etcd-config:latest $IMAGE
|
||||
docker push $IMAGE
|
||||
|
||||
- name: Build and push client image
|
||||
working-directory: ./tests/antithesis
|
||||
run: |
|
||||
make antithesis-build-client-docker-image
|
||||
export IMAGE="${{ env.REGISTRY }}/${{ env.REPOSITORY }}/etcd-client:${{ inputs.etcd_ref }}_${{ github.sha }}"
|
||||
docker tag etcd-client:latest $IMAGE
|
||||
docker push $IMAGE
|
||||
|
||||
- name: Build and push etcd image
|
||||
working-directory: ./tests/antithesis
|
||||
run: |
|
||||
make antithesis-build-etcd-image REF=${{ inputs.etcd_ref }}
|
||||
export IMAGE="${{ env.REGISTRY }}/${{ env.REPOSITORY }}/etcd-server:${{ inputs.etcd_ref }}_${{ github.sha }}"
|
||||
docker tag etcd-server:latest $IMAGE
|
||||
docker push $IMAGE
|
||||
|
||||
- name: Run Antithesis Tests
|
||||
uses: antithesishq/antithesis-trigger-action@b7d0c9d1d9316bd4de73a44144c56636ea3a64ba # main commit on Mar 13, 2025
|
||||
with:
|
||||
notebook_name: etcd
|
||||
tenant: linuxfoundation
|
||||
username: ${{ secrets.ANTITHESIS_WEBHOOK_USERNAME }}
|
||||
password: ${{ secrets.ANTITHESIS_WEBHOOK_PASSWORD }}
|
||||
github_token: ${{ secrets.GH_PAT }}
|
||||
config_image: us-central1-docker.pkg.dev/molten-verve-216720/linuxfoundation-repository/etcd-config:${{ inputs.etcd_ref }}_${{ github.sha }}
|
||||
images: us-central1-docker.pkg.dev/molten-verve-216720/linuxfoundation-repository/etcd-client:${{ inputs.etcd_ref }}_${{ github.sha }};us-central1-docker.pkg.dev/molten-verve-216720/linuxfoundation-repository/etcd-server:${{ inputs.etcd_ref }}_${{ github.sha }};docker.io/library/ubuntu:latest;us-central1-docker.pkg.dev/molten-verve-216720/linuxfoundation-repository/etcd-config:${{ inputs.etcd_ref }}_${{ github.sha }}
|
||||
description: ${{ inputs.description }}
|
||||
email_recipients: ${{ inputs.email }}
|
||||
test_name: ${{ inputs.test }}
|
||||
additional_parameters: |-
|
||||
custom.duration = ${{ inputs.duration }}
|
||||
antithesis.source = ${{ inputs.etcd_ref }}
|
|
@ -40,7 +40,7 @@ jobs:
|
|||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8
|
||||
uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
|
||||
with:
|
||||
# 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.
|
||||
|
@ -50,6 +50,6 @@ jobs:
|
|||
# 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@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8
|
||||
uses: github/codeql-action/autobuild@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8
|
||||
uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
---
|
||||
name: Test contrib/mixin
|
||||
on: [push, pull_request]
|
||||
permissions: read-all
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- id: goversion
|
||||
run: echo "goversion=$(cat .go-version)" >> "$GITHUB_OUTPUT"
|
||||
- uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
with:
|
||||
go-version: ${{ steps.goversion.outputs.goversion }}
|
||||
- run: |
|
||||
set -euo pipefail
|
||||
|
||||
make -C contrib/mixin tools test
|
|
@ -1,34 +0,0 @@
|
|||
---
|
||||
name: Coverage
|
||||
on: [push, pull_request]
|
||||
permissions: read-all
|
||||
jobs:
|
||||
coverage:
|
||||
# this is to prevent the job to run at forked projects
|
||||
if: github.repository == 'etcd-io/etcd'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- linux-amd64-coverage
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- id: goversion
|
||||
run: echo "goversion=$(cat .go-version)" >> "$GITHUB_OUTPUT"
|
||||
- uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
with:
|
||||
go-version: ${{ steps.goversion.outputs.goversion }}
|
||||
- env:
|
||||
TARGET: ${{ matrix.target }}
|
||||
run: |
|
||||
mkdir "${TARGET}"
|
||||
case "${TARGET}" in
|
||||
linux-amd64-coverage)
|
||||
GOARCH=amd64 ./scripts/codecov_upload.sh
|
||||
;;
|
||||
*)
|
||||
echo "Failed to find target"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
|
@ -1,26 +0,0 @@
|
|||
---
|
||||
name: Fuzzing v3rpc
|
||||
on: [push, pull_request]
|
||||
permissions: read-all
|
||||
jobs:
|
||||
fuzzing:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
env:
|
||||
TARGET_PATH: ./server/etcdserver/api/v3rpc
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- id: goversion
|
||||
run: echo "goversion=$(cat .go-version)" >> "$GITHUB_OUTPUT"
|
||||
- uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
with:
|
||||
go-version: ${{ steps.goversion.outputs.goversion }}
|
||||
- run: |
|
||||
set -euo pipefail
|
||||
|
||||
GOARCH=amd64 CPU=4 make fuzz
|
||||
- uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
if: failure()
|
||||
with:
|
||||
path: "${{env.TARGET_PATH}}/testdata/fuzz/**/*"
|
|
@ -1,38 +0,0 @@
|
|||
---
|
||||
name: grpcProxy-tests
|
||||
on: [push, pull_request]
|
||||
permissions: read-all
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- linux-amd64-grpcproxy-integration
|
||||
- linux-amd64-grpcproxy-e2e
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- id: goversion
|
||||
run: echo "goversion=$(cat .go-version)" >> "$GITHUB_OUTPUT"
|
||||
- uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
with:
|
||||
go-version: ${{ steps.goversion.outputs.goversion }}
|
||||
- env:
|
||||
TARGET: ${{ matrix.target }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
echo "${TARGET}"
|
||||
case "${TARGET}" in
|
||||
linux-amd64-grpcproxy-integration)
|
||||
GOOS=linux GOARCH=amd64 CPU=4 make test-grpcproxy-integration
|
||||
;;
|
||||
linux-amd64-grpcproxy-e2e)
|
||||
GOOS=linux GOARCH=amd64 CPU=4 make test-grpcproxy-e2e
|
||||
;;
|
||||
*)
|
||||
echo "Failed to find target"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
|
@ -15,7 +15,7 @@ jobs:
|
|||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- id: goversion
|
||||
run: echo "goversion=$(cat .go-version)" >> "$GITHUB_OUTPUT"
|
||||
- uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: ${{ steps.goversion.outputs.goversion }}
|
||||
- env:
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
---
|
||||
name: Release
|
||||
on: [push, pull_request]
|
||||
permissions: read-all
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- id: goversion
|
||||
run: echo "goversion=$(cat .go-version)" >> "$GITHUB_OUTPUT"
|
||||
- uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
|
||||
with:
|
||||
go-version: ${{ steps.goversion.outputs.goversion }}
|
||||
- name: release
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
git config --global user.email "github-action@etcd.io"
|
||||
git config --global user.name "Github Action"
|
||||
gpg --batch --gen-key <<EOF
|
||||
%no-protection
|
||||
Key-Type: 1
|
||||
Key-Length: 2048
|
||||
Subkey-Type: 1
|
||||
Subkey-Length: 2048
|
||||
Name-Real: Github Action
|
||||
Name-Email: github-action@etcd.io
|
||||
Expire-Date: 0
|
||||
EOF
|
||||
DRY_RUN=true ./scripts/release.sh --no-upload --no-docker-push --no-gh-release --in-place 3.6.99
|
||||
- name: test-image
|
||||
run: |
|
||||
VERSION=3.6.99 ./scripts/test_images.sh
|
||||
- name: save-image
|
||||
run: |
|
||||
docker image save -o /tmp/etcd-img.tar gcr.io/etcd-development/etcd
|
||||
- name: upload-image
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: etcd-img
|
||||
path: /tmp/etcd-img.tar
|
||||
retention-days: 1
|
||||
trivy-scan:
|
||||
needs: main
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platforms: [amd64, arm64, ppc64le, s390x]
|
||||
permissions:
|
||||
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: get-image
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
with:
|
||||
name: etcd-img
|
||||
path: /tmp
|
||||
- name: load-image
|
||||
run: |
|
||||
docker load < /tmp/etcd-img.tar
|
||||
- name: trivy-scan
|
||||
uses: aquasecurity/trivy-action@18f2510ee396bbf400402947b394f2dd8c87dbb0 # v0.29.0
|
||||
with:
|
||||
image-ref: 'gcr.io/etcd-development/etcd:v3.6.99-${{ matrix.platforms }}'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
format: 'sarif'
|
||||
output: 'trivy-results-${{ matrix.platforms }}.sarif'
|
||||
env:
|
||||
# Use AWS' ECR mirror for the trivy-db image, as GitHub's Container
|
||||
# Registry is returning a TOOMANYREQUESTS error.
|
||||
# Ref: https://github.com/aquasecurity/trivy-action/issues/389
|
||||
TRIVY_DB_REPOSITORY: 'public.ecr.aws/aquasecurity/trivy-db:2'
|
||||
- name: upload scan results
|
||||
uses: github/codeql-action/upload-sarif@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8
|
||||
with:
|
||||
sarif_file: 'trivy-results-${{ matrix.platforms }}.sarif'
|
|
@ -28,7 +28,7 @@ jobs:
|
|||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
|
||||
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
|
@ -42,7 +42,7 @@ jobs:
|
|||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
|
@ -50,6 +50,6 @@ jobs:
|
|||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8
|
||||
uses: github/codeql-action/upload-sarif@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
name: Mark and close stale issues and PRs
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 #v9.1.0
|
||||
with:
|
||||
days-until-stale: 90
|
||||
days-until-close: 21
|
||||
stale-issue-label: 'stale'
|
||||
stale-pr-label: 'stale'
|
||||
exempt-labels: 'stage/tracked,help wanted'
|
||||
exempt-projects: false
|
||||
exempt-milestones: false
|
||||
exempt-assignees: false
|
||||
stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed after 21 days if no further activity occurs. Thank you for your contributions.'
|
||||
stale-pr-message: 'This pull request has been automatically marked as stale because it has not had recent activity. It will be closed after 21 days if no further activity occurs. Thank you for your contributions.'
|
||||
close-issue-message: ''
|
||||
close-pr-message: ''
|
||||
operations-per-run: 30
|
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
name: Verify released binary assets
|
||||
permissions: read-all
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
verify-assets:
|
||||
name: Verify released binary assets
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Verify binary assets
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RELEASE: ${{ github.event.release.tag_name }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
run: |
|
||||
mkdir github-assets
|
||||
pushd github-assets
|
||||
gh --repo "${REPOSITORY}" release download "${RELEASE}"
|
||||
|
||||
test_assets() {
|
||||
if [ "$(wc -l <SHA256SUMS)" != "$(find . -name 'etcd-*' | wc -l)" ]; then
|
||||
echo "::error:: Invalid number of assets"
|
||||
exit 1
|
||||
fi
|
||||
sha256sum -c SHA256SUMS
|
||||
}
|
||||
test_assets
|
||||
popd
|
||||
|
||||
mkdir google-assets
|
||||
for file in github-assets/*; do
|
||||
file=$(basename "${file}")
|
||||
echo "Downloading ${file} from Google..."
|
||||
curl "https://storage.googleapis.com/etcd/${RELEASE}/${file}" \
|
||||
--fail \
|
||||
-o "google-assets/${file}"
|
||||
done
|
||||
pushd google-assets
|
||||
|
||||
test_assets
|
|
@ -1 +1 @@
|
|||
1.23.6
|
||||
1.24.4
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v2.3.8](https://github.com/etcd-io/etcd/releases/tag/v2.3.8) (2017-02-17)
|
||||
|
@ -12,5 +12,5 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v2.3.7...v2.3.8).
|
|||
- Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.0.16](https://github.com/etcd-io/etcd/releases/tag/v3.0.16) (2016-11-13)
|
||||
|
@ -14,7 +14,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.15...v3.0.16) an
|
|||
- Compile with [*Go 1.6.4*](https://golang.org/doc/devel/release.html#go1.6).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.0.15](https://github.com/etcd-io/etcd/releases/tag/v3.0.15) (2016-11-11)
|
||||
|
@ -32,7 +32,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.14...v3.0.15) an
|
|||
- Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.0.14](https://github.com/etcd-io/etcd/releases/tag/v3.0.14) (2016-11-04)
|
||||
|
@ -50,7 +50,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.13...v3.0.14) an
|
|||
- Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.0.13](https://github.com/etcd-io/etcd/releases/tag/v3.0.13) (2016-10-24)
|
||||
|
@ -64,7 +64,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.12...v3.0.13) an
|
|||
- Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.0.12](https://github.com/etcd-io/etcd/releases/tag/v3.0.12) (2016-10-07)
|
||||
|
@ -78,7 +78,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.11...v3.0.12) an
|
|||
- Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.0.11](https://github.com/etcd-io/etcd/releases/tag/v3.0.11) (2016-10-07)
|
||||
|
@ -98,7 +98,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.10...v3.0.11) an
|
|||
- Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.0.10](https://github.com/etcd-io/etcd/releases/tag/v3.0.10) (2016-09-23)
|
||||
|
@ -112,7 +112,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.9...v3.0.10) and
|
|||
- Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.0.9](https://github.com/etcd-io/etcd/releases/tag/v3.0.9) (2016-09-15)
|
||||
|
@ -130,7 +130,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.8...v3.0.9) and
|
|||
- Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.0.8](https://github.com/etcd-io/etcd/releases/tag/v3.0.8) (2016-09-09)
|
||||
|
@ -148,7 +148,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.7...v3.0.8) and
|
|||
- Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.0.7](https://github.com/etcd-io/etcd/releases/tag/v3.0.7) (2016-08-31)
|
||||
|
@ -166,7 +166,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.6...v3.0.7) and
|
|||
- Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.0.6](https://github.com/etcd-io/etcd/releases/tag/v3.0.6) (2016-08-19)
|
||||
|
@ -180,7 +180,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.5...v3.0.6) and
|
|||
- Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.0.5](https://github.com/etcd-io/etcd/releases/tag/v3.0.5) (2016-08-19)
|
||||
|
@ -198,7 +198,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.4...v3.0.5) and
|
|||
- Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.0.4](https://github.com/etcd-io/etcd/releases/tag/v3.0.4) (2016-07-27)
|
||||
|
@ -221,7 +221,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.3...v3.0.4) and
|
|||
- Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.0.3](https://github.com/etcd-io/etcd/releases/tag/v3.0.3) (2016-07-15)
|
||||
|
@ -241,7 +241,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.2...v3.0.3) and
|
|||
- Compile with [*Go 1.6.2*](https://golang.org/doc/devel/release.html#go1.6).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.0.2](https://github.com/etcd-io/etcd/releases/tag/v3.0.2) (2016-07-08)
|
||||
|
@ -259,7 +259,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.1...v3.0.2) and
|
|||
- Compile with [*Go 1.6.2*](https://golang.org/doc/devel/release.html#go1.6).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.0.1](https://github.com/etcd-io/etcd/releases/tag/v3.0.1) (2016-07-01)
|
||||
|
@ -273,7 +273,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.0...v3.0.1) and
|
|||
- Compile with [*Go 1.6.2*](https://golang.org/doc/devel/release.html#go1.6).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.0.0](https://github.com/etcd-io/etcd/releases/tag/v3.0.0) (2016-06-30)
|
||||
|
@ -287,5 +287,5 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v2.3.0...v3.0.0) and
|
|||
- Compile with [*Go 1.6.2*](https://golang.org/doc/devel/release.html#go1.6).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Previous change logs can be found at [CHANGELOG-3.0](https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.0.md).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## [v3.1.21](https://github.com/etcd-io/etcd/releases/tag/v3.1.21) (2019-TBD)
|
||||
|
||||
|
@ -23,7 +23,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
|
||||
- Fix bug where [db_compaction_total_duration_milliseconds metric incorrectly measured duration as 0](https://github.com/etcd-io/etcd/pull/10646).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## [v3.1.20](https://github.com/etcd-io/etcd/releases/tag/v3.1.20) (2018-10-10)
|
||||
|
||||
|
@ -69,7 +69,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.1.19](https://github.com/etcd-io/etcd/releases/tag/v3.1.19) (2018-07-24)
|
||||
|
@ -115,7 +115,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.1.18](https://github.com/etcd-io/etcd/releases/tag/v3.1.18) (2018-06-15)
|
||||
|
@ -138,7 +138,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.1.17](https://github.com/etcd-io/etcd/releases/tag/v3.1.17) (2018-06-06)
|
||||
|
@ -159,7 +159,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.16...v3.1.17) an
|
|||
- Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.1.16](https://github.com/etcd-io/etcd/releases/tag/v3.1.16) (2018-05-31)
|
||||
|
@ -179,7 +179,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.15...v3.1.16) an
|
|||
- Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.1.15](https://github.com/etcd-io/etcd/releases/tag/v3.1.15) (2018-05-09)
|
||||
|
@ -199,7 +199,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.14...v3.1.15) an
|
|||
- Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.1.14](https://github.com/etcd-io/etcd/releases/tag/v3.1.14) (2018-04-24)
|
||||
|
@ -233,7 +233,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.1.13](https://github.com/etcd-io/etcd/releases/tag/v3.1.13) (2018-03-29)
|
||||
|
@ -261,7 +261,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.1.12](https://github.com/etcd-io/etcd/releases/tag/v3.1.12) (2018-03-08)
|
||||
|
@ -284,7 +284,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.11...v3.1.12) an
|
|||
- Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.1.11](https://github.com/etcd-io/etcd/releases/tag/v3.1.11) (2017-11-28)
|
||||
|
@ -303,7 +303,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.10...v3.1.11) an
|
|||
- Compile with [*Go 1.8.5*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.1.10](https://github.com/etcd-io/etcd/releases/tag/v3.1.10) (2017-07-14)
|
||||
|
@ -323,7 +323,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.9...v3.1.10) and
|
|||
- Fix panic on `net/http.CloseNotify`
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.1.9](https://github.com/etcd-io/etcd/releases/tag/v3.1.9) (2017-06-09)
|
||||
|
@ -341,7 +341,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.8...v3.1.9) and
|
|||
- Compile with [*Go 1.7.6*](https://golang.org/doc/devel/release.html#go1.7).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.1.8](https://github.com/etcd-io/etcd/releases/tag/v3.1.8) (2017-05-19)
|
||||
|
@ -355,7 +355,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.7...v3.1.8) and
|
|||
- Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.1.7](https://github.com/etcd-io/etcd/releases/tag/v3.1.7) (2017-04-28)
|
||||
|
@ -369,7 +369,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.6...v3.1.7) and
|
|||
- Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.1.6](https://github.com/etcd-io/etcd/releases/tag/v3.1.6) (2017-04-19)
|
||||
|
@ -388,7 +388,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.5...v3.1.6) and
|
|||
- Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.1.5](https://github.com/etcd-io/etcd/releases/tag/v3.1.5) (2017-03-27)
|
||||
|
@ -411,7 +411,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.4...v3.1.5) and
|
|||
- Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.1.4](https://github.com/etcd-io/etcd/releases/tag/v3.1.4) (2017-03-22)
|
||||
|
@ -425,7 +425,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.3...v3.1.4) and
|
|||
- Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.1.3](https://github.com/etcd-io/etcd/releases/tag/v3.1.3) (2017-03-10)
|
||||
|
@ -452,7 +452,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.2...v3.1.3) and
|
|||
- Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.1.2](https://github.com/etcd-io/etcd/releases/tag/v3.1.2) (2017-02-24)
|
||||
|
@ -474,7 +474,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.1...v3.1.2) and
|
|||
- Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.1.1](https://github.com/etcd-io/etcd/releases/tag/v3.1.1) (2017-02-17)
|
||||
|
@ -488,7 +488,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.0...v3.1.1) and
|
|||
- Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.1.0](https://github.com/etcd-io/etcd/releases/tag/v3.1.0) (2017-01-20)
|
||||
|
@ -570,5 +570,5 @@ See [security doc](https://etcd.io/docs/latest/op-guide/security/) for more deta
|
|||
- Compile with [*Go 1.7.4*](https://golang.org/doc/devel/release.html#go1.7).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ Previous change logs can be found at [CHANGELOG-3.1](https://github.com/etcd-io/
|
|||
|
||||
## v3.2.33 (TBD)
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## [v3.2.32](https://github.com/etcd-io/etcd/releases/tag/v3.2.32) (2021-03-28)
|
||||
See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.31...v3.2.32) and [v3.2 upgrade guide](https://etcd.io/docs/latest/upgrades/upgrade_3_2/) for any breaking changes.
|
||||
|
@ -23,7 +23,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.31...v3.2.32) an
|
|||
- Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.31](https://github.com/etcd-io/etcd/releases/tag/v3.2.31) (2020-08-18)
|
||||
|
@ -49,7 +49,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.30...v3.2.31) an
|
|||
- Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.30](https://github.com/etcd-io/etcd/releases/tag/v3.2.30) (2020-04-01)
|
||||
|
@ -69,7 +69,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.29...v3.2.30) an
|
|||
- Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.29](https://github.com/etcd-io/etcd/releases/tag/v3.2.29) (2020-03-18)
|
||||
|
@ -97,7 +97,7 @@ See [List of metrics](https://github.com/etcd-io/etcd/tree/main/Documentation/me
|
|||
- Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.28](https://github.com/etcd-io/etcd/releases/tag/v3.2.28) (2019-11-10)
|
||||
|
@ -125,7 +125,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.27](https://github.com/etcd-io/etcd/releases/tag/v3.2.27) (2019-09-17)
|
||||
|
@ -161,7 +161,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.26](https://github.com/etcd-io/etcd/releases/tag/v3.2.26) (2019-01-11)
|
||||
|
@ -183,7 +183,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.25...v3.2.26) an
|
|||
- Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.25](https://github.com/etcd-io/etcd/releases/tag/v3.2.25) (2018-10-10)
|
||||
|
@ -230,7 +230,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.24](https://github.com/etcd-io/etcd/releases/tag/v3.2.24) (2018-07-24)
|
||||
|
@ -286,7 +286,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.23](https://github.com/etcd-io/etcd/releases/tag/v3.2.23) (2018-06-15)
|
||||
|
@ -317,7 +317,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.22](https://github.com/etcd-io/etcd/releases/tag/v3.2.22) (2018-06-06)
|
||||
|
@ -339,7 +339,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.21...v3.2.22) an
|
|||
- Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.21](https://github.com/etcd-io/etcd/releases/tag/v3.2.21) (2018-05-31)
|
||||
|
@ -360,7 +360,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.20...v3.2.21) an
|
|||
- Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.20](https://github.com/etcd-io/etcd/releases/tag/v3.2.20) (2018-05-09)
|
||||
|
@ -380,7 +380,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.19...v3.2.20) an
|
|||
- Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.19](https://github.com/etcd-io/etcd/releases/tag/v3.2.19) (2018-04-24)
|
||||
|
@ -423,7 +423,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.18](https://github.com/etcd-io/etcd/releases/tag/v3.2.18) (2018-03-29)
|
||||
|
@ -451,7 +451,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.17](https://github.com/etcd-io/etcd/releases/tag/v3.2.17) (2018-03-08)
|
||||
|
@ -481,7 +481,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.16...v3.2.17) an
|
|||
- Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.16](https://github.com/etcd-io/etcd/releases/tag/v3.2.16) (2018-02-12)
|
||||
|
@ -504,7 +504,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.15...v3.2.16) an
|
|||
- Compile with [*Go 1.8.5*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.15](https://github.com/etcd-io/etcd/releases/tag/v3.2.15) (2018-01-22)
|
||||
|
@ -523,7 +523,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.14...v3.2.15) an
|
|||
- Compile with [*Go 1.8.5*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.14](https://github.com/etcd-io/etcd/releases/tag/v3.2.14) (2018-01-11)
|
||||
|
@ -545,7 +545,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.13...v3.2.14) an
|
|||
- Compile with [*Go 1.8.5*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.13](https://github.com/etcd-io/etcd/releases/tag/v3.2.13) (2018-01-02)
|
||||
|
@ -564,7 +564,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.12...v3.2.13) an
|
|||
- Compile with [*Go 1.8.5*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.12](https://github.com/etcd-io/etcd/releases/tag/v3.2.12) (2017-12-20)
|
||||
|
@ -596,7 +596,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.11...v3.2.12) an
|
|||
- Compile with [*Go 1.8.5*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.11](https://github.com/etcd-io/etcd/releases/tag/v3.2.11) (2017-12-05)
|
||||
|
@ -629,7 +629,7 @@ See [security doc](https://etcd.io/docs/latest/op-guide/security/) for more deta
|
|||
- Compile with [*Go 1.8.5*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.10](https://github.com/etcd-io/etcd/releases/tag/v3.2.10) (2017-11-16)
|
||||
|
@ -663,7 +663,7 @@ See [security doc](https://etcd.io/docs/latest/op-guide/security/) for more deta
|
|||
- Compile with [*Go 1.8.5*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.9](https://github.com/etcd-io/etcd/releases/tag/v3.2.9) (2017-10-06)
|
||||
|
@ -685,7 +685,7 @@ See [security doc](https://etcd.io/docs/latest/op-guide/security/) for more deta
|
|||
- Compile with [*Go 1.8.4*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.8](https://github.com/etcd-io/etcd/releases/tag/v3.2.8) (2017-09-29)
|
||||
|
@ -707,7 +707,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.7...v3.2.8) and
|
|||
- Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.7](https://github.com/etcd-io/etcd/releases/tag/v3.2.7) (2017-09-01)
|
||||
|
@ -730,7 +730,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.6...v3.2.7) and
|
|||
- Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.6](https://github.com/etcd-io/etcd/releases/tag/v3.2.6) (2017-08-21)
|
||||
|
@ -758,7 +758,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.5](https://github.com/etcd-io/etcd/releases/tag/v3.2.5) (2017-08-04)
|
||||
|
@ -798,7 +798,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.4](https://github.com/etcd-io/etcd/releases/tag/v3.2.4) (2017-07-19)
|
||||
|
@ -820,7 +820,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.3...v3.2.4) and
|
|||
- Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.3](https://github.com/etcd-io/etcd/releases/tag/v3.2.3) (2017-07-14)
|
||||
|
@ -843,7 +843,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.2...v3.2.3) and
|
|||
- Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.2](https://github.com/etcd-io/etcd/releases/tag/v3.2.2) (2017-07-07)
|
||||
|
@ -879,7 +879,7 @@ See [security doc](https://etcd.io/docs/latest/op-guide/security/) for more deta
|
|||
- Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.1](https://github.com/etcd-io/etcd/releases/tag/v3.2.1) (2017-06-23)
|
||||
|
@ -909,7 +909,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.2.0](https://github.com/etcd-io/etcd/releases/tag/v3.2.0) (2017-06-09)
|
||||
|
@ -1017,5 +1017,5 @@ See [security doc](https://etcd.io/docs/latest/op-guide/security/) for more deta
|
|||
- Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Previous change logs can be found at [CHANGELOG-3.2](https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.2.md).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.3.27 (2021-10-15)
|
||||
|
||||
|
@ -16,7 +16,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.26...v3.3.27) an
|
|||
- [CVE-2019-9893](https://nvd.nist.gov/vuln/detail/CVE-2019-9893): incorrect syscall argument generation in libseccomp
|
||||
- [CVE-2021-36159](https://nvd.nist.gov/vuln/detail/CVE-2021-36159): libfetch in apk-tools mishandles numeric strings in FTP and HTTP protocols to allow out of bound reads.
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.3.26 (2021-10-03)
|
||||
|
||||
|
@ -34,7 +34,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.25...v3.3.26) an
|
|||
|
||||
- Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.3.25 (2020-08-24)
|
||||
|
||||
|
@ -68,7 +68,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.23...v3.3.24) an
|
|||
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
@ -96,7 +96,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.22...v3.3.23) an
|
|||
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.3.22](https://github.com/etcd-io/etcd/releases/tag/v3.3.22) (2020-05-20)
|
||||
|
@ -113,7 +113,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.21...v3.3.22) an
|
|||
- Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.3.21](https://github.com/etcd-io/etcd/releases/tag/v3.3.21) (2020-05-18)
|
||||
|
@ -150,7 +150,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.20...v3.3.21) an
|
|||
- Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.3.20](https://github.com/etcd-io/etcd/releases/tag/v3.3.20) (2020-04-01)
|
||||
|
@ -170,7 +170,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.19...v3.3.20) an
|
|||
- Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.3.19](https://github.com/etcd-io/etcd/releases/tag/v3.3.19) (2020-03-18)
|
||||
|
@ -206,7 +206,7 @@ See [List of metrics](https://github.com/etcd-io/etcd/tree/main/Documentation/me
|
|||
- Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.3.18](https://github.com/etcd-io/etcd/releases/tag/v3.3.18) (2019-11-26)
|
||||
|
@ -227,7 +227,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- Now, etcd makes sure the purge file loop exits before server signals stop of the raft node.
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.3.17](https://github.com/etcd-io/etcd/releases/tag/v3.3.17) (2019-10-11)
|
||||
|
@ -241,7 +241,7 @@ This release replaces 3.3.16.
|
|||
Due to the etcd 3.3.16 release being incorrectly released (see details below), please use this release instead.
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.3.16](https://github.com/etcd-io/etcd/releases/tag/v3.3.16) (2019-10-10)
|
||||
|
@ -290,7 +290,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- Fix ["1.16: etcd client does not parse IPv6 addresses correctly when members are joining" (kubernetes#83550)](https://github.com/kubernetes/kubernetes/issues/83550).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.3.15](https://github.com/etcd-io/etcd/releases/tag/v3.3.15) (2019-08-19)
|
||||
|
@ -313,7 +313,7 @@ NOTE: This patch release had to include some new features from 3.4, while trying
|
|||
- Compile with [*Go 1.12.9*](https://golang.org/doc/devel/release.html#go1.12) including [*Go 1.12.8*](https://groups.google.com/d/msg/golang-announce/65QixT3tcmg/DrFiG6vvCwAJ) security fixes.
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.3.14](https://github.com/etcd-io/etcd/releases/tag/v3.3.14) (2019-08-16)
|
||||
|
@ -416,7 +416,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- Compile with [*Go 1.12.9*](https://golang.org/doc/devel/release.html#go1.12) including [*Go 1.12.8*](https://groups.google.com/d/msg/golang-announce/65QixT3tcmg/DrFiG6vvCwAJ) security fixes.
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.3.13](https://github.com/etcd-io/etcd/releases/tag/v3.3.13) (2019-05-02)
|
||||
|
@ -456,7 +456,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- Compile with [*Go 1.10.8*](https://golang.org/doc/devel/release.html#go1.10).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.3.12](https://github.com/etcd-io/etcd/releases/tag/v3.3.12) (2019-02-07)
|
||||
|
@ -474,7 +474,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.11...v3.3.12) an
|
|||
- Compile with [*Go 1.10.8*](https://golang.org/doc/devel/release.html#go1.10).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.3.11](https://github.com/etcd-io/etcd/releases/tag/v3.3.11) (2019-01-11)
|
||||
|
@ -496,7 +496,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.10...v3.3.11) an
|
|||
- Compile with [*Go 1.10.7*](https://golang.org/doc/devel/release.html#go1.10).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.3.10](https://github.com/etcd-io/etcd/releases/tag/v3.3.10) (2018-10-10)
|
||||
|
@ -542,7 +542,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- Compile with [*Go 1.10.4*](https://golang.org/doc/devel/release.html#go1.10).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.3.9](https://github.com/etcd-io/etcd/releases/tag/v3.3.9) (2018-07-24)
|
||||
|
@ -597,7 +597,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- Compile with [*Go 1.10.3*](https://golang.org/doc/devel/release.html#go1.10).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.3.8](https://github.com/etcd-io/etcd/releases/tag/v3.3.8) (2018-06-15)
|
||||
|
@ -619,7 +619,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.7...v3.3.8) and
|
|||
- Compile with [*Go 1.9.7*](https://golang.org/doc/devel/release.html#go1.9).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.3.7](https://github.com/etcd-io/etcd/releases/tag/v3.3.7) (2018-06-06)
|
||||
|
@ -645,7 +645,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.6...v3.3.7) and
|
|||
- Compile with [*Go 1.9.6*](https://golang.org/doc/devel/release.html#go1.9).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.3.6](https://github.com/etcd-io/etcd/releases/tag/v3.3.6) (2018-05-31)
|
||||
|
@ -668,7 +668,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.5...v3.3.6) and
|
|||
- Compile with [*Go 1.9.6*](https://golang.org/doc/devel/release.html#go1.9).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.3.5](https://github.com/etcd-io/etcd/releases/tag/v3.3.5) (2018-05-09)
|
||||
|
@ -687,7 +687,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.4...v3.3.5) and
|
|||
- Compile with [*Go 1.9.6*](https://golang.org/doc/devel/release.html#go1.9).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.3.4](https://github.com/etcd-io/etcd/releases/tag/v3.3.4) (2018-04-24)
|
||||
|
@ -735,7 +735,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- Compile with [*Go 1.9.5*](https://golang.org/doc/devel/release.html#go1.9).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.3.3](https://github.com/etcd-io/etcd/releases/tag/v3.3.3) (2018-03-29)
|
||||
|
@ -772,7 +772,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- Compile with [*Go 1.9.5*](https://golang.org/doc/devel/release.html#go1.9).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.3.2](https://github.com/etcd-io/etcd/releases/tag/v3.3.2) (2018-03-08)
|
||||
|
@ -805,7 +805,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.1...v3.3.2) and
|
|||
- Compile with [*Go 1.9.4*](https://golang.org/doc/devel/release.html#go1.9).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.3.1](https://github.com/etcd-io/etcd/releases/tag/v3.3.1) (2018-02-12)
|
||||
|
@ -833,7 +833,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.0...v3.3.1) and
|
|||
- Compile with [*Go 1.9.4*](https://golang.org/doc/devel/release.html#go1.9).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.3.0](https://github.com/etcd-io/etcd/releases/tag/v3.3.0) (2018-02-01)
|
||||
|
@ -1117,5 +1117,5 @@ See [security doc](https://etcd.io/docs/latest/op-guide/security/) for more deta
|
|||
- Deprecate [`golang.org/x/net/context`](https://github.com/etcd-io/etcd/pull/8511).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
|
|
@ -2,9 +2,30 @@
|
|||
|
||||
Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.3.md).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.4.36 (TBC)
|
||||
## v3.4.38 (TBC)
|
||||
|
||||
### etcd server
|
||||
|
||||
- Fix [mvcc: avoid double decrement of watcher gauge on close/cancel race](https://github.com/etcd-io/etcd/pull/20065)
|
||||
- Fix [Watch on future revision returns old events or notifications](https://github.com/etcd-io/etcd/pull/20291)
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Compile binaries using [go 1.23.11](https://github.com/etcd-io/etcd/pull/20322).
|
||||
|
||||
---
|
||||
|
||||
## v3.4.37 (2025-04-15)
|
||||
|
||||
### Dependencies
|
||||
- Bump [golang.org/x/net to v0.36.0 to address CVE-2025-22870](https://github.com/etcd-io/etcd/pull/19529).
|
||||
- Compile binaries using [go 1.23.8](https://github.com/etcd-io/etcd/pull/19726)
|
||||
|
||||
---
|
||||
|
||||
## v3.4.36 (2025-02-25)
|
||||
|
||||
### etcd server
|
||||
- [Avoid deadlock in etcd.Close when stopping during bootstrapping](https://github.com/etcd-io/etcd/pull/19166)
|
||||
|
@ -14,9 +35,11 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
|
|||
- Fix [runtime panic that occurs when KeepAlive is called with a Context implemented by an uncomparable type](https://github.com/etcd-io/etcd/pull/18936)
|
||||
|
||||
### Dependencies
|
||||
- Compile binaries using [go 1.22.12](https://github.com/etcd-io/etcd/pull/19337)
|
||||
- Compile binaries using [go 1.23.6](https://github.com/etcd-io/etcd/pull/19429)
|
||||
- Bump golang.org/x/crypto to v0.35.0 to address [CVE-2024-45337](https://github.com/etcd-io/etcd/pull/19197) and [CVE-2025-22869](https://github.com/etcd-io/etcd/pull/19477).
|
||||
- Bump golang.org/x/net to v0.34.0 to address [CVE-2024-45338](https://github.com/etcd-io/etcd/pull/19197).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.4.35 (2024-11-12)
|
||||
|
||||
|
@ -28,7 +51,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
|
|||
### Dependencies
|
||||
- Compile binaries using [go 1.22.9](https://github.com/etcd-io/etcd/pull/18850).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.4.34 (2024-09-11)
|
||||
|
||||
|
@ -43,7 +66,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
|
|||
- Compile binaries using [go 1.22.7](https://github.com/etcd-io/etcd/pull/18549).
|
||||
- Upgrade [bbolt to 1.3.11](https://github.com/etcd-io/etcd/pull/18488).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.4.33 (2024-06-13)
|
||||
|
||||
|
@ -54,7 +77,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
|
|||
- Compile binaries using go [1.21.11](https://github.com/etcd-io/etcd/pull/18130).
|
||||
- Upgrade [bbolt to 1.3.10](https://github.com/etcd-io/etcd/pull/17945).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.4.32 (2024-04-25)
|
||||
|
||||
|
@ -73,7 +96,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
|
|||
### Dependencies
|
||||
- Compile binaries using [go 1.21.9](https://github.com/etcd-io/etcd/pull/17709).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.4.31 (2024-03-21)
|
||||
|
||||
|
@ -96,7 +119,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
|
|||
### Others
|
||||
- [Make CGO_ENABLED configurable](https://github.com/etcd-io/etcd/pull/17422).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.4.30 (2024-01-31)
|
||||
|
||||
|
@ -107,7 +130,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
|
|||
- Compile binaries using go [1.20.13](https://github.com/etcd-io/etcd/pull/17276).
|
||||
- Upgrade [golang.org/x/crypto to v0.17+ to address CVE-2023-48795](https://github.com/etcd-io/etcd/pull/17347).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.4.29 (2024-01-09)
|
||||
|
||||
|
@ -121,7 +144,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
|
|||
### Dependencies
|
||||
- Compile binaries using go [1.20.12](https://github.com/etcd-io/etcd/pull/17076).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.4.28 (2023-11-23)
|
||||
|
||||
|
@ -143,7 +166,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
|
|||
- Upgrade [bbolt to 1.3.8](https://github.com/etcd-io/etcd/pull/16834).
|
||||
- Upgrade gRPC to 1.58.3 in https://github.com/etcd-io/etcd/pull/16997 and https://github.com/etcd-io/etcd/pull/16999. Note that gRPC server will reject requests with connection header (refer to https://github.com/grpc/grpc-go/pull/4803).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.4.27 (2023-07-11)
|
||||
|
||||
|
@ -158,7 +181,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
|
|||
### Dependencies
|
||||
- Compile binaries using [go 1.19.10](https://github.com/etcd-io/etcd/pull/16038).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.4.26 (2023-05-12)
|
||||
|
||||
|
@ -169,7 +192,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
|
|||
### Dependencies
|
||||
- Compile binaries using [go 1.19.9](https://github.com/etcd-io/etcd/pull/15823)
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.4.25 (2023-04-14)
|
||||
|
||||
|
@ -196,7 +219,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
|
|||
### Docker image
|
||||
- Fix [etcd docker images all tagged with amd64 architecture](https://github.com/etcd-io/etcd/pull/15681)
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.4.24 (2023-02-16)
|
||||
|
||||
|
@ -218,7 +241,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
|
|||
### Docker image
|
||||
- Updated [base image from base-debian11 to static-debian11 and removed dependency on busybox](https://github.com/etcd-io/etcd/pull/15038).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.4.23 (2022-12-21)
|
||||
|
||||
|
@ -237,7 +260,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
|
|||
### Docker image
|
||||
- Use [distroless base image](https://github.com/etcd-io/etcd/pull/15017) to address critical Vulnerabilities.
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.4.22 (2022-11-02)
|
||||
|
||||
|
@ -256,7 +279,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
|
|||
### etcd grpc-proxy
|
||||
- Add [`etcd grpc-proxy start --listen-cipher-suites`](https://github.com/etcd-io/etcd/pull/14601) flag to support adding configurable cipher list.
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.4.21 (2022-09-15)
|
||||
|
||||
|
@ -269,7 +292,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
|
|||
|
||||
- Fix [etcdctl move-leader may fail for multiple endpoints](https://github.com/etcd-io/etcd/pull/14441)
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.4.20 (2022-08-06)
|
||||
|
||||
|
@ -289,7 +312,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
|
|||
- Fix [Grant lease with negative ID can possibly cause db out of sync](https://github.com/etcd-io/etcd/pull/14239)
|
||||
- Fix [Allow non mutating requests pass through quotaKVServer when NOSPACE](https://github.com/etcd-io/etcd/pull/14254)
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.4.19 (2022-07-12)
|
||||
|
||||
|
@ -317,7 +340,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.18...v3.4.19) an
|
|||
- Compile with [Go 1.16+](https://go.dev/doc/devel/release#go1.16).
|
||||
- etcd uses [go modules](https://github.com/etcd-io/etcd/pull/14136) (instead of vendor dir) to track dependencies.
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.4.18 (2021-10-15)
|
||||
|
||||
|
@ -337,7 +360,7 @@ See [List of metrics](https://etcd.io/docs/latest/metrics/) for all metrics per
|
|||
- [CVE-2019-9893](https://nvd.nist.gov/vuln/detail/CVE-2019-9893): incorrect syscall argument generation in libseccomp
|
||||
- [CVE-2021-36159](https://nvd.nist.gov/vuln/detail/CVE-2021-36159): libfetch in apk-tools mishandles numeric strings in FTP and HTTP protocols to allow out of bound reads.
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.4.17 (2021-10-03)
|
||||
|
||||
|
@ -359,7 +382,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.16...v3.4.17) an
|
|||
|
||||
- Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.4.16 (2021-05-11)
|
||||
|
||||
|
@ -381,7 +404,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.15...v3.4.16) an
|
|||
- Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.4.15](https://github.com/etcd-io/etcd/releases/tag/v3.4.15) (2021-02-26)
|
||||
|
@ -406,7 +429,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.14...v3.4.15) an
|
|||
- Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.4.14](https://github.com/etcd-io/etcd/releases/tag/v3.4.14) (2020-11-25)
|
||||
|
@ -436,7 +459,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.13...v3.4.14) an
|
|||
- Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.4.13](https://github.com/etcd-io/etcd/releases/tag/v3.4.13) (2020-8-24)
|
||||
|
@ -452,7 +475,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.12...v3.4.13) an
|
|||
- Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.4.12](https://github.com/etcd-io/etcd/releases/tag/v3.4.12) (2020-08-19)
|
||||
|
@ -470,7 +493,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.11...v3.4.12) an
|
|||
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
@ -504,7 +527,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.10...v3.4.11) an
|
|||
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
@ -533,7 +556,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.9...v3.4.10) and
|
|||
- Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.4.9](https://github.com/etcd-io/etcd/releases/tag/v3.4.9) (2020-05-20)
|
||||
|
@ -550,7 +573,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.8...v3.4.9) and
|
|||
- Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.4.8](https://github.com/etcd-io/etcd/releases/tag/v3.4.8) (2020-05-18)
|
||||
|
@ -587,7 +610,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.7...v3.4.8) and
|
|||
- Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.4.7](https://github.com/etcd-io/etcd/releases/tag/v3.4.7) (2020-04-01)
|
||||
|
@ -611,7 +634,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.6...v3.4.7) and
|
|||
- Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.4.6](https://github.com/etcd-io/etcd/releases/tag/v3.4.6) (2020-03-29)
|
||||
|
@ -629,7 +652,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.5...v3.4.6) and
|
|||
- Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.4.5](https://github.com/etcd-io/etcd/releases/tag/v3.4.5) (2020-03-18)
|
||||
|
@ -666,7 +689,7 @@ See [List of metrics](https://etcd.io/docs/latest/metrics/) for all metrics per
|
|||
- Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.4.4](https://github.com/etcd-io/etcd/releases/tag/v3.4.4) (2020-02-24)
|
||||
|
@ -699,7 +722,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- Fix bug where [some auth related messages are logged at wrong level](https://github.com/etcd-io/etcd/pull/11586)
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.4.3](https://github.com/etcd-io/etcd/releases/tag/v3.4.3) (2019-10-24)
|
||||
|
@ -721,7 +744,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- Compile with [*Go 1.12.12*](https://golang.org/doc/devel/release.html#go1.12).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.4.2](https://github.com/etcd-io/etcd/releases/tag/v3.4.2) (2019-10-11)
|
||||
|
@ -750,7 +773,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.1...v3.4.2) and
|
|||
- Fix ["1.16: etcd client does not parse IPv6 addresses correctly when members are joining" (kubernetes#83550)](https://github.com/kubernetes/kubernetes/issues/83550).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## [v3.4.1](https://github.com/etcd-io/etcd/releases/tag/v3.4.1) (2019-09-17)
|
||||
|
@ -786,7 +809,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- Compile with [*Go 1.12.9*](https://golang.org/doc/devel/release.html#go1.12) including [*Go 1.12.8*](https://groups.google.com/d/msg/golang-announce/65QixT3tcmg/DrFiG6vvCwAJ) security fixes.
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
||||
## v3.4.0 (2019-08-30)
|
||||
|
@ -1391,5 +1414,5 @@ Note: **v3.5 will deprecate `etcd --log-package-levels` flag for `capnslog`**; `
|
|||
|
||||
- [Rebase etcd image from Alpine to Debian](https://github.com/etcd-io/etcd/pull/10805) to improve security and maintenance effort for etcd release.
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
|
|
@ -1,20 +1,69 @@
|
|||
|
||||
|
||||
Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.4.md).
|
||||
<hr>
|
||||
|
||||
## v3.5.19 (TBC)
|
||||
---
|
||||
|
||||
## v3.5.22 (TBC)
|
||||
|
||||
### etcd server
|
||||
|
||||
- Fix [the compaction pause duration metric is not emitted for every compaction batch](https://github.com/etcd-io/etcd/pull/19771)
|
||||
- Fix [mvcc: avoid double decrement of watcher gauge on close/cancel race](https://github.com/etcd-io/etcd/pull/20066)
|
||||
- Fix [Watch on future revision returns old events or notifications](https://github.com/etcd-io/etcd/pull/20290)
|
||||
|
||||
### Package `clientv3`
|
||||
|
||||
- [Replace `resolver.State.Addresses` with `resolver.State.Endpoint.Addresses`](https://github.com/etcd-io/etcd/pull/19783).
|
||||
- [Deprecated the Metadata field in the Endpoint struct from the client/v3/naming/endpoints package](https://github.com/etcd-io/etcd/pull/19846).
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Compile binaries using [go 1.23.11](https://github.com/etcd-io/etcd/pull/20321)
|
||||
|
||||
---
|
||||
|
||||
## v3.5.21 (2025-03-27)
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Bump [github.com/golang-jwt/jwt/v4 from 4.5.1 to 4.5.2 to address CVE-2025-30204](https://github.com/etcd-io/etcd/pull/19646).
|
||||
- Bump [bump golang.org/x/net from v0.36.0 to v0.38.0 to address CVE-2025-22870 and CVE-2025-22872](https://github.com/etcd-io/etcd/pull/19686).
|
||||
|
||||
---
|
||||
|
||||
## v3.5.20 (2025-03-21)
|
||||
|
||||
### etcd server
|
||||
|
||||
- Fix [the learner promotion changes not being persisted into v3store (bbolt)](https://github.com/etcd-io/etcd/pull/19563)
|
||||
- Update [the RLock in Demoted method for read-only access to expiry](https://github.com/etcd-io/etcd/pull/19445)
|
||||
|
||||
### etcdctl
|
||||
|
||||
- Fix [command `etcdctl member promote` doesn't support json output](https://github.com/etcd-io/etcd/pull/19602)
|
||||
|
||||
### etcd grpc-proxy
|
||||
|
||||
- Fix [grpcproxy can get stuck in and endless loop causing high CPU usage](https://github.com/etcd-io/etcd/pull/19562)
|
||||
|
||||
---
|
||||
|
||||
## v3.5.19 (2025-03-05)
|
||||
|
||||
### etcd server
|
||||
- Backport [add learner status check to readyz endpoint](https://github.com/etcd-io/etcd/pull/19280).
|
||||
- Fix [performance regression due to uncertain compaction sleep interval](https://github.com/etcd-io/etcd/pull/19405).
|
||||
|
||||
### `tools/benchmark`
|
||||
- Backport [add mixed read-write performance evaluation scripts](https://github.com/etcd-io/etcd/pull/19275).
|
||||
|
||||
### Dependencies
|
||||
- Compile binaries using [go 1.22.12](https://github.com/etcd-io/etcd/pull/19336).
|
||||
- Compile binaries using [go 1.23.7](https://github.com/etcd-io/etcd/pull/19528).
|
||||
- Bump [golang.org/x/crypto to v0.35.0 to address CVE-2025-22869](https://github.com/etcd-io/etcd/pull/19478).
|
||||
- Bump [golang.org/x/net to v0.36.0 to address CVE-2025-22870](https://github.com/etcd-io/etcd/pull/19530).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.5.18 (2025-01-24)
|
||||
|
||||
|
@ -38,7 +87,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
|
|||
- Bump [golang.org/x/crypto to 0.32.0 to address CVE-2024-45337](https://github.com/etcd-io/etcd/pull/19154).
|
||||
- Bump [golang.org/x/net to 0.34.0 to address CVE-2024-45338](https://github.com/etcd-io/etcd/pull/19158).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.5.17 (2024-11-12)
|
||||
|
||||
|
@ -51,7 +100,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
|
|||
### Dependencies
|
||||
- Compile binaries using [go 1.22.9](https://github.com/etcd-io/etcd/pull/18849).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.5.16 (2024-09-10)
|
||||
|
||||
|
@ -64,7 +113,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
|
|||
- Compile binaries using [go 1.22.7](https://github.com/etcd-io/etcd/pull/18550).
|
||||
- Upgrade [bbolt to v1.3.11](https://github.com/etcd-io/etcd/pull/18489).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.5.15 (2024-07-19)
|
||||
|
||||
|
@ -103,7 +152,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
|
|||
- Compile binaries using [go 1.21.10](https://github.com/etcd-io/etcd/pull/17980).
|
||||
- Upgrade [bbolt to v1.3.10](https://github.com/etcd-io/etcd/pull/17943).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.5.13 (2024-03-29)
|
||||
|
||||
|
@ -132,7 +181,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
|
|||
### Others
|
||||
- [Make CGO_ENABLED configurable](https://github.com/etcd-io/etcd/pull/17421).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.5.12 (2024-01-31)
|
||||
|
||||
|
@ -159,7 +208,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
|
|||
- Compile binaries using [go 1.20.12](https://github.com/etcd-io/etcd/pull/17077)
|
||||
- Fix [CVE-2023-47108](https://github.com/advisories/GHSA-8pgv-569h-w5rw) by [bumping go.opentelemetry.io/otel to 1.20.0 and go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc to 0.46.0](https://github.com/etcd-io/etcd/pull/16946).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.5.10 (2023-10-27)
|
||||
|
||||
|
@ -190,7 +239,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
|
|||
- Upgrade gRPC to 1.58.3 in https://github.com/etcd-io/etcd/pull/16625, https://github.com/etcd-io/etcd/pull/16781 and https://github.com/etcd-io/etcd/pull/16790. Note that gRPC server will reject requests with connection header (refer to https://github.com/grpc/grpc-go/pull/4803).
|
||||
- Upgrade [bbolt to v1.3.8](https://github.com/etcd-io/etcd/pull/16833)
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.5.9 (2023-05-11)
|
||||
|
||||
|
@ -200,7 +249,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
|
|||
### Dependencies
|
||||
- Compile binaries using [go 1.19.9](https://github.com/etcd-io/etcd/pull/15822).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.5.8 (2023-04-13)
|
||||
|
||||
|
@ -233,7 +282,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
|
|||
- [Remove nsswitch.conf from docker image](https://github.com/etcd-io/etcd/pull/15161)
|
||||
- Fix [etcd docker images all tagged with amd64 architecture](https://github.com/etcd-io/etcd/pull/15612)
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.5.7 (2023-01-20)
|
||||
|
||||
|
@ -256,7 +305,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
|
|||
- Use [distroless base image](https://github.com/etcd-io/etcd/pull/15016) to address critical Vulnerabilities.
|
||||
- Updated [base image from base-debian11 to static-debian11 and removed dependency on busybox](https://github.com/etcd-io/etcd/pull/15037).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.5.6 (2022-11-21)
|
||||
|
||||
|
@ -276,7 +325,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
|
|||
### etcd grpc-proxy
|
||||
- Add [`etcd grpc-proxy start --listen-cipher-suites`](https://github.com/etcd-io/etcd/pull/14500) flag to support adding configurable cipher list.
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.5.5 (2022-09-15)
|
||||
|
||||
|
@ -311,7 +360,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
|
|||
- [Bump golang.org/x/crypto to latest version](https://github.com/etcd-io/etcd/pull/13996) to address [CVE-2022-27191](https://github.com/advisories/GHSA-8c26-wmh5-6g9v).
|
||||
- [Bump OpenTelemetry to 1.0.1 and gRPC to 1.41.0](https://github.com/etcd-io/etcd/pull/14312).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.5.4 (2022-04-24)
|
||||
|
||||
|
@ -323,7 +372,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
|
|||
- [Revert the change of trimming the trailing dot from SRV.Target](https://github.com/etcd-io/etcd/pull/13950) returned by DNS lookup
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.5.3 (2022-04-13)
|
||||
|
||||
|
@ -344,7 +393,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
|
|||
|
||||
- [Always print the raft_term in decimal](https://github.com/etcd-io/etcd/pull/13727) when displaying member list in json.
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## [v3.5.2](https://github.com/etcd-io/etcd/releases/tag/v3.5.2) (2022-02-01)
|
||||
|
||||
|
@ -357,7 +406,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.5.1...v3.5.2) and
|
|||
- Fix [assertion failed due to tx closed when recovering v3 backend from a snapshot db](https://github.com/etcd-io/etcd/pull/13501)
|
||||
- Fix [segmentation violation(SIGSEGV) error due to premature unlocking of watchableStore](https://github.com/etcd-io/etcd/pull/13541)
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## [v3.5.1](https://github.com/etcd-io/etcd/releases/tag/v3.5.1) (2021-10-15)
|
||||
|
||||
|
@ -384,7 +433,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.5.0...v3.5.1) and
|
|||
- [CVE-2019-9893](https://nvd.nist.gov/vuln/detail/CVE-2019-9893): incorrect syscall argument generation in libseccomp
|
||||
- [CVE-2021-36159](https://nvd.nist.gov/vuln/detail/CVE-2021-36159): libfetch in apk-tools mishandles numeric strings in FTP and HTTP protocols to allow out of bound reads.
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.5.0 (2021-06)
|
||||
|
||||
|
@ -681,5 +730,5 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
|
|||
- The etcd team has added, a well defined and openly discussed, project [governance](https://github.com/etcd-io/etcd/pull/11175).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
|
|
@ -2,9 +2,130 @@
|
|||
|
||||
Previous change logs can be found at [CHANGELOG-3.5](https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.5.md).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v3.6.0 (TBD)
|
||||
## v3.6.3 (TBD)
|
||||
|
||||
---
|
||||
|
||||
## v3.6.2 (2025-07-09)
|
||||
|
||||
### etcd server
|
||||
|
||||
- Fix [Watch on future revision returns old events or notifications](https://github.com/etcd-io/etcd/pull/20286)
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [Bump bbolt to v1.4.2](https://github.com/etcd-io/etcd/pull/20267)
|
||||
- Compile binaries using [go 1.23.11](https://github.com/etcd-io/etcd/pull/20314).
|
||||
|
||||
---
|
||||
|
||||
## v3.6.1 (2025-06-06)
|
||||
|
||||
### etcd server
|
||||
|
||||
- [Replaced the deprecated/removed `UnaryServerInterceptor` and `StreamServerInterceptor` in otelgrpc with `NewServerHandler`](https://github.com/etcd-io/etcd/pull/20043)
|
||||
- [Add protection on `PromoteMember` and `UpdateRaftAttributes` to prevent panicking](https://github.com/etcd-io/etcd/pull/20051)
|
||||
- [Fix the issue that `--force-new-cluster` can't remove all other members in a corner case](https://github.com/etcd-io/etcd/pull/20071)
|
||||
- Fix [mvcc: avoid double decrement of watcher gauge on close/cancel race](https://github.com/etcd-io/etcd/pull/20067)
|
||||
- [Add validation to ensure there is no empty v3discovery endpoint](https://github.com/etcd-io/etcd/pull/20113)
|
||||
|
||||
### etcdctl
|
||||
|
||||
- Fix [command `etcdctl endpoint health` doesn't work when options are set via environment variables](https://github.com/etcd-io/etcd/pull/20121)
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Compile binaries using [go 1.23.10](https://github.com/etcd-io/etcd/pull/20128).
|
||||
|
||||
---
|
||||
|
||||
## v3.6.0 (2025-05-15)
|
||||
|
||||
There isn't any production code change since v3.6.0-rc.5.
|
||||
|
||||
---
|
||||
|
||||
## v3.6.0-rc.5 (2025-05-08)
|
||||
|
||||
### etcd server
|
||||
|
||||
- Fix [the compaction pause duration metric is not emitted for every compaction batch](https://github.com/etcd-io/etcd/pull/19770)
|
||||
|
||||
### Package `clientv3`
|
||||
|
||||
- [Replace `resolver.State.Addresses` with `resolver.State.Endpoint.Addresses`](https://github.com/etcd-io/etcd/pull/19782).
|
||||
- [Deprecated the Metadata field in the Endpoint struct from the client/v3/naming/endpoints package](https://github.com/etcd-io/etcd/pull/19842).
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Compile binaries using [go 1.23.9](https://github.com/etcd-io/etcd/pull/19867).
|
||||
|
||||
---
|
||||
|
||||
## v3.6.0-rc.4 (2025-04-15)
|
||||
|
||||
### etcd server
|
||||
|
||||
- [Switch to validating v3 when v2 and v3 are synchronized](https://github.com/etcd-io/etcd/pull/19703).
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Compile binaries using [go 1.23.8](https://github.com/etcd-io/etcd/pull/19724)
|
||||
|
||||
---
|
||||
|
||||
## v3.6.0-rc.3 (2025-03-27)
|
||||
|
||||
### etcd server
|
||||
|
||||
- [Auto sync members in v3store for the issues which have already been affected by #19557](https://github.com/etcd-io/etcd/pull/19636).
|
||||
- [Move `client/internal/v2` into `server/internel/clientv2`](https://github.com/etcd-io/etcd/pull/19673).
|
||||
- [Replace ExperimentalMaxLearners with a Feature Gate](https://github.com/etcd-io/etcd/pull/19560).
|
||||
|
||||
### etcd grpc-proxy
|
||||
|
||||
- Fix [grpcproxy can get stuck in and endless loop causing high CPU usage](https://github.com/etcd-io/etcd/pull/19562)
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Bump [github.com/golang-jwt/jwt/v5 from 5.2.1 to 5.2.2 to address CVE-2025-30204](https://github.com/etcd-io/etcd/pull/19647).
|
||||
- Bump [bump golang.org/x/net from v0.37.0 to v0.38.0 to address CVE-2025-22872](https://github.com/etcd-io/etcd/pull/19687).
|
||||
|
||||
---
|
||||
|
||||
## v3.6.0-rc.2 (2025-03-05)
|
||||
|
||||
### etcd server
|
||||
|
||||
- Add [Prometheus metric to query server feature gates](https://github.com/etcd-io/etcd/pull/19495).
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Compile binaries using [go 1.23.7](https://github.com/etcd-io/etcd/pull/19527).
|
||||
- Bump [golang.org/x/net to v0.36.0 to address CVE-2025-22870](https://github.com/etcd-io/etcd/pull/19531).
|
||||
- Bump [github.com/grpc-ecosystem/grpc-gateway/v2 to v2.26.3 to fix the issue of etcdserver crashing on receiving REST watch stream requests](https://github.com/etcd-io/etcd/pull/19522).
|
||||
|
||||
---
|
||||
|
||||
## v3.6.0-rc.1 (2025-02-25)
|
||||
|
||||
### etcdctl v3
|
||||
|
||||
- Add [`DowngradeInfo` in result of endpoint status](https://github.com/etcd-io/etcd/pull/19471)
|
||||
|
||||
### etcd server
|
||||
|
||||
- Add [`DowngradeInfo` to endpoint status response](https://github.com/etcd-io/etcd/pull/19471)
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Bump [golang.org/x/crypto to v0.35.0 to address CVE-2025-22869](https://github.com/etcd-io/etcd/pull/19480).
|
||||
|
||||
---
|
||||
|
||||
## v3.6.0-rc.0 (2025-02-13)
|
||||
|
||||
See [code changes](https://github.com/etcd-io/etcd/compare/v3.5.0...v3.6.0).
|
||||
|
||||
|
@ -15,6 +136,19 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.5.0...v3.6.0).
|
|||
- `etcdctl` will sleep(2s) in case of range delete without `--range` flag. See [pull/13747](https://github.com/etcd-io/etcd/pull/13747)
|
||||
- Applications which depend on etcd v3.6 packages must be built with go version >= v1.18.
|
||||
|
||||
#### Flags Removed
|
||||
|
||||
- The following flags have been removed:
|
||||
|
||||
- `--enable-v2`
|
||||
- `--experimental-enable-v2v3`
|
||||
- `--proxy`
|
||||
- `--proxy-failure-wait`
|
||||
- `--proxy-refresh-interval`
|
||||
- `--proxy-dial-timeout`
|
||||
- `--proxy-write-timeout`
|
||||
- `--proxy-read-timeout`
|
||||
|
||||
### Deprecations
|
||||
|
||||
- Deprecated [V2 discovery](https://etcd.io/docs/v3.5/dev-internal/discovery_protocol/).
|
||||
|
@ -22,8 +156,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.5.0...v3.6.0).
|
|||
- Removed [etcdctl defrag --data-dir](https://github.com/etcd-io/etcd/pull/13793).
|
||||
- Removed [etcdctl snapshot status](https://github.com/etcd-io/etcd/pull/13809).
|
||||
- Removed [etcdctl snapshot restore](https://github.com/etcd-io/etcd/pull/13809).
|
||||
- Removed [etcdutl snapshot save](https://github.com/etcd-io/etcd/pull/13809).
|
||||
|
||||
- Removed [NewZapCoreLoggerBuilder in server/embed](https://github.com/etcd-io/etcd/pull/19404)
|
||||
|
||||
### etcdctl v3
|
||||
|
||||
|
@ -78,7 +211,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.5.0...v3.6.0).
|
|||
- Decreased [`--snapshot-count` default value from 100,000 to 10,000](https://github.com/etcd-io/etcd/pull/15408)
|
||||
- Add [`etcd --tls-min-version --tls-max-version`](https://github.com/etcd-io/etcd/pull/15156) to enable support for TLS 1.3.
|
||||
- Add [quota to endpoint status response](https://github.com/etcd-io/etcd/pull/17877)
|
||||
- Add ['etcd --experimental-set-member-localaddr'](https://github.com/etcd-io/etcd/pull/17661) to enable using the first specified and non-loopback local address from initial-advertise-peer-urls as the local address when communicating with a peer.
|
||||
- Add [feature gate `SetMemberLocalAddr`](https://github.com/etcd-io/etcd/pull/19413) to [enable using the first specified and non-loopback local address from initial-advertise-peer-urls as the local address when communicating with a peer]((https://github.com/etcd-io/etcd/pull/17661))
|
||||
- Add [Support multiple values for allowed client and peer TLS identities](https://github.com/etcd-io/etcd/pull/18015)
|
||||
- Add [`embed.Config.GRPCAdditionalServerOptions`](https://github.com/etcd-io/etcd/pull/14066) to support updating the default internal gRPC configuration for embedded use cases.
|
||||
|
||||
|
@ -110,4 +243,4 @@ See [List of metrics](https://etcd.io/docs/latest/metrics/) for all metrics per
|
|||
- [Upgrade grpc-gateway from v1 to v2](https://github.com/etcd-io/etcd/pull/16595).
|
||||
- [Switch from grpc-ecosystem/go-grpc-prometheus to grpc-ecosystem/go-grpc-middleware/providers/prometheus](https://github.com/etcd-io/etcd/pull/19195).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
|
||||
Previous change logs can be found at [CHANGELOG-3.6](https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.6.md).
|
||||
|
||||
---
|
||||
|
||||
## v3.7.0 (TBD)
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- [Removed all deprecated experimental flags](https://github.com/etcd-io/etcd/pull/19959)
|
||||
- [Removed v2discovery](https://github.com/etcd-io/etcd/pull/20109)
|
||||
- [Removed client/v2](https://github.com/etcd-io/etcd/pull/20117)
|
||||
|
||||
### etcd server
|
||||
|
||||
- [Update go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc to v0.61.0 and replaced the deprecated `UnaryServerInterceptor` and `StreamServerInterceptor` with `NewServerHandler`](https://github.com/etcd-io/etcd/pull/20017)
|
||||
- [Add Support for Unix Socket endpoints](https://github.com/etcd-io/etcd/pull/19760)
|
||||
|
||||
### Package `pkg`
|
||||
|
||||
- [Optimize find performance by splitting intervals with the same left endpoint by their right endpoints](https://github.com/etcd-io/etcd/pull/19768)
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Compile binaries using [go 1.24.4](https://github.com/etcd-io/etcd/pull/20163)
|
||||
|
||||
### Deprecations
|
||||
|
||||
- Deprecated [UsageFunc in pkg/cobrautl](https://github.com/etcd-io/etcd/pull/18356).
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Previous change logs can be found at [CHANGELOG-3.x](https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.x.md).
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
## v4.0.0 (TBD)
|
||||
|
||||
|
@ -40,5 +40,5 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.5.0...v4.0.0) and
|
|||
- Require [*Go 2*](https://blog.golang.org/go2draft).
|
||||
|
||||
|
||||
<hr>
|
||||
---
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
ARG ARCH=amd64
|
||||
FROM --platform=linux/${ARCH} gcr.io/distroless/static-debian12@sha256:3f2b64ef97bd285e36132c684e6b2ae8f2723293d09aae046196cca64251acac
|
||||
FROM --platform=linux/${ARCH} gcr.io/distroless/static-debian12@sha256:b7b9a6953e7bed6baaf37329331051d7bdc1b99c885f6dbeb72d75b1baad54f9
|
||||
|
||||
ADD etcd /usr/local/bin/
|
||||
ADD etcdctl /usr/local/bin/
|
||||
|
|
|
@ -0,0 +1,191 @@
|
|||
# Bump etcd Version in Kubernetes
|
||||
|
||||
This guide will walk through the update of etcd in Kubernetes to a new version (`kubernetes/kubernetes` repository).
|
||||
|
||||
> Currently we bump etcd v3.5.x for K8s release-1.33 and lower versions, and we bump etcd v3.6.x for K8s release-1.34 and higher versions.
|
||||
|
||||
You can use this [issue](https://github.com/kubernetes/kubernetes/issues/131101) as a reference when updating the etcd version in Kubernetes.
|
||||
|
||||
Bumping the etcd version in Kubernetes consists of two steps.
|
||||
|
||||
* Bump etcd client SDK
|
||||
* Bump etcd image
|
||||
|
||||
> The commented lines in this document signifies the line to be changed
|
||||
|
||||
## Bump etcd client SDK
|
||||
|
||||
> Reference: [link](https://github.com/kubernetes/kubernetes/pull/131103)
|
||||
|
||||
You can refer to the guide [here](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/vendor.md) under the **Adding or updating a dependency** section.
|
||||
|
||||
1. Get all the etcd modules used in Kubernetes.
|
||||
|
||||
```bash
|
||||
$ grep 'go.etcd.io/etcd/' go.mod | awk '{print $1}'
|
||||
go.etcd.io/etcd/api/v3
|
||||
go.etcd.io/etcd/client/pkg/v3
|
||||
go.etcd.io/etcd/client/v3
|
||||
go.etcd.io/etcd/client/v2
|
||||
go.etcd.io/etcd/pkg/v3
|
||||
go.etcd.io/etcd/raft/v3
|
||||
go.etcd.io/etcd/server/v3
|
||||
```
|
||||
|
||||
2. For each module, in the root directory of the `kubernetes/kubernetes` repository, fetch the new version in `go.mod` using the following command (using `client/v3` as an example):
|
||||
|
||||
```bash
|
||||
hack/pin-dependency.sh go.etcd.io/etcd/client/v3 NEW_VERSION
|
||||
```
|
||||
|
||||
3. Rebuild the `vendor` directory and update the `go.mod` files for all staging repositories using the command below. This automatically updates the licenses.
|
||||
|
||||
```bash
|
||||
hack/update-vendor.sh
|
||||
```
|
||||
|
||||
4. Check if the new dependency requires newer versions of existing dependencies we have pinned. You can check this by:
|
||||
|
||||
* Running `hack/lint-dependencies.sh` against your branch and against `master` and comparing the results.
|
||||
* Checking if any new `replace` directives were added to `go.mod` files of components inside the staging directory.
|
||||
|
||||
## Bump etcd image
|
||||
|
||||
### Build etcd image
|
||||
|
||||
> Reference: [link 1](https://github.com/kubernetes/kubernetes/pull/131105) [link 2](https://github.com/kubernetes/kubernetes/pull/131126)
|
||||
|
||||
1. In `build/dependencies.yaml`, update the `version` of `etcd-image` to the new version. Update `golang: etcd release version` if necessary.
|
||||
|
||||
```yaml
|
||||
- name: "etcd-image"
|
||||
# version: 3.5.17
|
||||
version: 3.5.21
|
||||
refPaths:
|
||||
- path: cluster/images/etcd/Makefile
|
||||
match: BUNDLED_ETCD_VERSIONS\?|
|
||||
---
|
||||
- name: "golang: etcd release version"
|
||||
# version: 1.22.9
|
||||
version: 1.23.7 # https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.6.md
|
||||
```
|
||||
|
||||
2. In `cluster/images/etcd/Makefile`, include the new version in `BUNDLED_ETCD_VERSIONS` and update the `LATEST_ETCD_VERSION` as well (the image tag will be generated from the `LATEST_ETCD_VERSION`). Update `GOLANG_VERSION` according to the version used to compile that release version (`"golang: etcd release version"` in step 1).
|
||||
|
||||
```Makefile
|
||||
# BUNDLED_ETCD_VERSIONS?=3.4.18 3.5.17
|
||||
BUNDLED_ETCD_VERSIONS?=3.4.18 3.5.21
|
||||
|
||||
# LATEST_ETCD_VERSION?=3.5.17
|
||||
LATEST_ETCD_VERSION?=3.5.21
|
||||
|
||||
# GOLANG_VERSION := 1.22.9
|
||||
GOLANG_VERSION := 1.23.7
|
||||
```
|
||||
|
||||
3. In `cluster/images/etcd/migrate/options.go`, include the new version in the `supportedEtcdVersions` slice.
|
||||
|
||||
```go
|
||||
var (
|
||||
// supportedEtcdVersions = []string{"3.4.18", "3.5.17"}
|
||||
supportedEtcdVersions = []string{"3.4.18", "3.5.21"}
|
||||
)
|
||||
```
|
||||
|
||||
### Publish etcd image
|
||||
|
||||
> Reference: [link](https://github.com/kubernetes/k8s.io/pull/7957)
|
||||
|
||||
1. When the previous step is merged, a post-commit job will run to build the image. You can find the newly built image in the [registry](https://gcr.io/k8s-staging-etcd/etcd).
|
||||
|
||||
2. Locate the newly built image and copy its SHA256 digest.
|
||||
|
||||
3. Inside the `kubernetes/k8s.io` repository, in `registry.k8s.io/images/k8s-staging-etcd/images.yaml`, create a new entry for the desired version and copy the SHA256 digest.
|
||||
|
||||
```yaml
|
||||
"sha256:b4a9e4a7e1cf08844c7c4db6a19cab380fbf0aad702b8c01e578e9543671b9f9": ["3.5.17-0"]
|
||||
# ADD:
|
||||
"sha256:d58c035df557080a27387d687092e3fc2b64c6d0e3162dc51453a115f847d121": ["3.5.21-0"]
|
||||
```
|
||||
|
||||
### Update to use the new etcd image
|
||||
|
||||
> Reference: [link](https://github.com/kubernetes/kubernetes/pull/131144)
|
||||
|
||||
1. In `build/dependencies.yaml`, change the `version` of `etcd` to the new version.
|
||||
|
||||
```yaml
|
||||
# etcd
|
||||
- name: "etcd"
|
||||
# version: 3.5.17
|
||||
version: 3.5.21
|
||||
refPaths:
|
||||
- path: cluster/gce/manifests/etcd.manifest
|
||||
match: etcd_docker_tag|etcd_version
|
||||
```
|
||||
|
||||
2. In `cluster/gce/manifests/etcd.manifest`, change the image tag to the new image tag and `TARGET_VERSION` to the new version.
|
||||
|
||||
```manifest
|
||||
// "image": "{{ pillar.get('etcd_docker_repository', 'registry.k8s.io/etcd') }}:{{ pillar.get('etcd_docker_tag', '3.5.17-0') }}",
|
||||
|
||||
"image": "{{ pillar.get('etcd_docker_repository', 'registry.k8s.io/etcd') }}:{{ pillar.get('etcd_docker_tag', '3.5.21-0') }}",
|
||||
|
||||
---
|
||||
|
||||
{ "name": "TARGET_VERSION",
|
||||
// "value": "{{ pillar.get('etcd_version', '3.5.17') }}"
|
||||
"value": "{{ pillar.get('etcd_version', '3.5.21') }}"
|
||||
},
|
||||
```
|
||||
|
||||
3. In `cluster/gce/upgrade-aliases.sh`, update the exports for `ETCD_IMAGE` to the new image tag and `ETCD_VERSION` to the new version.
|
||||
|
||||
```sh
|
||||
# export ETCD_IMAGE=3.5.17-0
|
||||
export ETCD_IMAGE=3.5.21-0
|
||||
# export ETCD_VERSION=3.5.17
|
||||
export ETCD_VERSION=3.5.21
|
||||
```
|
||||
|
||||
4. In `cmd/kubeadm/app/constants/constants.go`, change the `DefaultEtcdVersion` to the new version. In the same file, update `SupportedEtcdVersion` accordingly.
|
||||
|
||||
```go
|
||||
// DefaultEtcdVersion = "3.5.17-0"
|
||||
DefaultEtcdVersion = "3.5.21-0"
|
||||
|
||||
---
|
||||
|
||||
SupportedEtcdVersion = map[uint8]string{
|
||||
// 30: "3.5.17-0",
|
||||
// 31: "3.5.17-0",
|
||||
// 32: "3.5.17-0",
|
||||
// 33: "3.5.17-0",
|
||||
30: "3.5.21-0",
|
||||
31: "3.5.21-0",
|
||||
32: "3.5.21-0",
|
||||
33: "3.5.21-0",
|
||||
}
|
||||
```
|
||||
|
||||
5. In `hack/lib/etcd.sh`, update the `ETCD_VERSION`.
|
||||
|
||||
```sh
|
||||
# ETCD_VERSION=${ETCD_VERSION:-3.5.17}
|
||||
ETCD_VERSION=${ETCD_VERSION:-3.5.21}
|
||||
```
|
||||
|
||||
6. In `staging/src/k8s.io/sample-apiserver/artifacts/example/deployment.yaml`, update the etcd image used.
|
||||
|
||||
```yaml
|
||||
- name: etcd
|
||||
# image: gcr.io/etcd-development/etcd:v3.5.17
|
||||
image: gcr.io/etcd-development/etcd:v3.5.21
|
||||
```
|
||||
|
||||
7. In `test/utils/image/manifest.go`, update the etcd image tag.
|
||||
|
||||
```go
|
||||
// configs[Etcd] = Config{list.GcEtcdRegistry, "etcd", "3.5.17-0"}
|
||||
configs[Etcd] = Config{list.GcEtcdRegistry, "etcd", "3.5.21-0"}
|
||||
```
|
|
@ -147,11 +147,11 @@ References:
|
|||
|
||||
[bbolt](https://github.com/etcd-io/bbolt) and [raft](https://github.com/etcd-io/raft) are two core dependencies of etcd.
|
||||
|
||||
Both etcd 3.4.x and 3.5.x depend on bbolt 1.3.x, and etcd 3.6.x (`main` branch) depends on bbolt 1.4.x.
|
||||
Both etcd 3.4.x and 3.5.x depend on bbolt 1.3.x, and etcd 3.6.x depends on bbolt 1.4.x.
|
||||
|
||||
raft is included in the etcd repository for release-3.4 and release-3.5 branches, so etcd 3.4.x and 3.5.x do not depend on any
|
||||
external raft module. We moved raft into [a separate repository](https://github.com/etcd-io/raft) starting from 3.6 (`main` branch), and the first raft
|
||||
release will be v3.6.0, so etcd 3.6.x will depend on raft 3.6.x.
|
||||
external raft module. We moved raft into [a separate repository](https://github.com/etcd-io/raft) starting from 3.6, and the first raft
|
||||
release is v3.6.0, so etcd 3.6.0 depends on raft v3.6.0.
|
||||
|
||||
Please see the table below:
|
||||
|
||||
|
|
|
@ -4,80 +4,84 @@ This document provides an overview of etcd features and general development guid
|
|||
|
||||
## Overview
|
||||
|
||||
The etcd features fall into three stages, experimental, stable, and unsafe.
|
||||
The etcd features fall into three stages: Alpha, Beta, and GA.
|
||||
|
||||
### Experimental
|
||||
### Alpha
|
||||
|
||||
Any new feature is usually added as an experimental feature. An experimental feature is characterized as below:
|
||||
Any new feature is usually added as an Alpha feature. An Alpha feature is characterized as below:
|
||||
- Might be buggy due to a lack of user testing. Enabling the feature may not work as expected.
|
||||
- Disabled by default when added initially.
|
||||
- Disabled by default.
|
||||
- Support for such a feature may be dropped at any time without notice
|
||||
- Feature-related issues may be given lower priorities.
|
||||
- It can be removed in the next minor or major release without following the feature deprecation policy unless it graduates to a stable future.
|
||||
- It can be removed in the next minor or major release without following the feature deprecation policy unless it graduates to a more stable stage.
|
||||
|
||||
### Stable
|
||||
### Beta
|
||||
|
||||
A stable feature is characterized as below:
|
||||
A Beta feature is characterized as below:
|
||||
- Supported as part of the supported releases of etcd.
|
||||
- May be enabled by default.
|
||||
- Enabled by default.
|
||||
- Discontinuation of support must follow the feature deprecation policy.
|
||||
|
||||
### Unsafe
|
||||
### GA
|
||||
|
||||
Unsafe features are rare and listed under the `Unsafe feature:` section in the etcd usage documentation. By default, they are disabled. They should be used with caution following documentation. An unsafe feature can be removed in the next minor or major release without following the feature deprecation policy.
|
||||
A GA feature is characterized as below:
|
||||
- Supported as part of the supported releases of etcd.
|
||||
- Always enabled; you cannot disable it. The corresponding feature gate is no longer needed.
|
||||
- Discontinuation of support must follow the feature deprecation policy.
|
||||
|
||||
## Development Guidelines
|
||||
|
||||
### Adding a new feature
|
||||
|
||||
Any new enhancements to the etcd are typically added as an experimental feature. The general development requirements are listed below. They can be somewhat flexible depending on the scope of the feature and review discussions and will evolve over time.
|
||||
- Open an issue
|
||||
Any new enhancements to the etcd are typically added as an Alpha feature.
|
||||
|
||||
etcd follows the Kubernetes [KEP process](https://github.com/kubernetes/enhancements/blob/master/keps/sig-architecture/0000-kep-process/README.md) for new enhancements. The general development requirements are listed below. They can be somewhat flexible depending on the scope of the feature and review discussions and will evolve over time.
|
||||
- Open a [KEP](https://github.com/kubernetes/enhancements/issues) issue
|
||||
- It must provide a clear need for the proposed feature.
|
||||
- It should list development work items as checkboxes. There must be one work item towards future graduation to a stable future.
|
||||
- Label the issue with `type/feature` and `experimental`.
|
||||
- It should list development work items as checkboxes. There must be one work item towards future graduation to Beta.
|
||||
- Label the issue with `/sig etcd`.
|
||||
- Keep the issue open for tracking purposes until a decision is made on graduation.
|
||||
- Open a Pull Request (PR)
|
||||
- Open a [KEP](https://github.com/kubernetes/enhancements) Pull Request (PR).
|
||||
- The KEP template can be simplified for etcd.
|
||||
- It must provide clear graduation criteria for each stage.
|
||||
- The KEP doc should reside in [keps/sig-etcd](https://github.com/kubernetes/enhancements/tree/master/keps/sig-etcd/)
|
||||
- Open Pull Requests (PRs) in [etcd](https://github.com/etcd-io/etcd)
|
||||
- Provide unit tests. Integration tests are also recommended as possible.
|
||||
- Provide robust e2e test coverage. If the feature being added is complicated or quickly needed, maintainers can decide to go with e2e tests for basic coverage initially and have robust coverage added at a later time before the feature graduation to the stable feature.
|
||||
- Provide logs for proper debugging.
|
||||
- Provide metrics and benchmarks as needed.
|
||||
- The Feature should be disabled by default.
|
||||
- Any configuration flags related to the implementation of the feature must be prefixed with `experimental` e.g. `--experimental-feature-name`.
|
||||
- Add an Alpha [feature gate](https://etcd.io/docs/v3.6/feature-gates/).
|
||||
- Any code changes or configuration flags related to the implementation of the feature must be gated with the feature gate e.g. `if cfg.ServerFeatureGate.Enabled(features.FeatureName)`.
|
||||
- Add a CHANGELOG entry.
|
||||
- At least two maintainers must approve feature requirements and related code changes.
|
||||
- At least two maintainers must approve the KEP and related code changes.
|
||||
|
||||
### Graduating an Experimental feature to Stable
|
||||
### Graduating a feature to the next stage
|
||||
|
||||
It is important that experimental features don't get stuck in that stage. They should be revisited and moved to the stable stage following the graduation steps as described here.
|
||||
|
||||
#### Locate graduation candidate
|
||||
Decide if an experimental feature is ready for graduation to the stable stage.
|
||||
- Find the issue that was used to enable the experimental feature initially. One way to find such issues is to search for issues with `type/feature` and `experimental` labels.
|
||||
- Fix any known open issues against the feature.
|
||||
- Make sure the feature was enabled for at least one previous release. Check the PR(s) reference from the issue to see when the feature-related code changes were merged.
|
||||
It is important that features don't get stuck in one stage. They should be revisited and moved to the next stage once they meet the graduation criteria listed in the KEP. A feature should stay at one stage for at least one release before being promoted.
|
||||
|
||||
#### Provide implementation
|
||||
If an experimental feature is found ready for graduation to the stable stage, open a Pull Request (PR) with the following changes.
|
||||
- Add robust e2e tests if not already provided.
|
||||
- Add a new stable feature flag identical to the experimental feature flag but without the `--experimental` prefix.
|
||||
- Deprecate the experimental feature following the [feature deprecation policy](#Deprecating-a-feature).
|
||||
- Implementation must ensure that both the graduated and deprecated experimental feature flags work as expected. Note that both these flags will co-exist for the timeframe described in the feature deprecation policy.
|
||||
- Enable the graduated feature by default if needed.
|
||||
|
||||
If a feature is found ready for graduation to the next stage, open a Pull Request (PR) with the following changes.
|
||||
- Update the feature `PreRelease` stage in `server/features/etcd_features.go`.
|
||||
- Update the status in the original KEP issue.
|
||||
|
||||
At least two maintainers must approve the work. Patch releases should not be considered for graduation.
|
||||
|
||||
### Deprecating a feature
|
||||
|
||||
#### Experimental
|
||||
An experimental feature deprecates when it graduates to the stable stage.
|
||||
- Add a deprecation message in the documentation of the experimental feature with a recommendation to use a related stable feature. e.g. `DEPRECATED. Use <feature-name> instead.`
|
||||
- Add a `deprecated` label in the issue that was initially used to enable the experimental feature.
|
||||
#### Alpha
|
||||
Alpha features can be removed without going through the deprecation process.
|
||||
- Remove the feature gate in `server/features/etcd_features.go`, and clean up all relevant code.
|
||||
- Close the original KEP issue with reasons to drop the feature.
|
||||
|
||||
#### Stable
|
||||
As the project evolves, a stable feature may sometimes need to be deprecated and removed. Such a situation should be handled using the steps below:
|
||||
- Create an issue for tracking purposes.
|
||||
- Add a deprecation message in the feature usage documentation before a planned release for feature deprecation. e.g. `To be deprecated in <release>.`. If a new feature replaces the `To be deprecated` feature, then also provide a message saying so. e.g. `Use <feature-name> instead.`.
|
||||
- Deprecate the feature in the planned release with a message as part of the feature usage documentation. e.g. `DEPRECATED`. If a new feature replaces the deprecated feature, then also provide a message saying so. e.g. `DEPRECATED. Use <feature-name> instead.`.
|
||||
- Add a `deprecated` label in the related issue.
|
||||
#### Beta and GA
|
||||
As the project evolves, a Beta/GA feature may sometimes need to be deprecated and removed. Such a situation should be handled using the steps below:
|
||||
|
||||
Remove the deprecated feature in the following release. Close any related issue(s). At least two maintainers must approve the work. Patch releases should not be considered for deprecation.
|
||||
- A Beta/GA feature can only be deprecated after at least 2 minor or major releases.
|
||||
- Update original KEP issue if it has not been closed or create a new etcd issue with reasons and steps to deprecate the feature.
|
||||
- Add the feature deprecation documentation in the release notes and feature gates documentation of the next minor/major release.
|
||||
- In the next minor/major release, set the feature gate to `{Default: false, PreRelease: featuregate.Deprecated, LockedToDefault: false}` in `server/features/etcd_features.go`. Deprecated feature gates must respond with a warning when used.
|
||||
- If the feature has GAed, and the original gated codes has been cleaned up, add the disablement codes back with the feature gate.
|
||||
- In the minor/major release after the next, set the feature gate to `{Default: false, PreRelease: featuregate.Deprecated, LockedToDefault: true}` in `server/features/etcd_features.go`, and start cleaning the code.
|
||||
|
||||
At least two maintainers must approve the work. Patch releases should not be considered for deprecation.
|
||||
|
|
|
@ -6,15 +6,17 @@ The procedure includes some manual steps for sanity checking, but it can probabl
|
|||
|
||||
## Release management
|
||||
|
||||
The following pool of release candidates manages the release of each etcd major/minor version as well as manages patches
|
||||
Under the leadership of **James Blair** [@jmhbnz](https://github.com/jmhbnz) and **Ivan Valdes Castillo** [@ivanvc](https://github.com/ivanvc), the following pool of release candidates manages the release of each etcd major/minor version as well as manages patches
|
||||
to each stable release branch. They are responsible for communicating the timelines and status of each release and
|
||||
for ensuring the stability of the release branch.
|
||||
|
||||
- Benjamin Wang [@ahrtr](https://github.com/ahrtr)
|
||||
- Fu Wei [@fuweid](https://github.com/fuweid)
|
||||
- James Blair [@jmhbnz](https://github.com/jmhbnz)
|
||||
- Ivan Valdes Castillo [@ivanvc](https://github.com/ivanvc)
|
||||
- Marek Siarkowicz [@serathius](https://github.com/serathius)
|
||||
- Sahdev Zala [@spzala](https://github.com/spzala)
|
||||
- Wenjia Zhang [@wenjiaswe](https://github.com/wenjiaswe)
|
||||
- Siyuan Zhang [@siyuanfoundation](https://github.com/siyuanfoundation)
|
||||
|
||||
All release version numbers follow the format of [semantic versioning 2.0.0](http://semver.org/).
|
||||
|
||||
|
@ -68,10 +70,14 @@ which don't need to be executed before releasing each version.
|
|||
|
||||
### Release steps
|
||||
|
||||
At least one day before the release:
|
||||
|
||||
1. Raise an issue to publish the release plan, e.g. [issues/17350](https://github.com/etcd-io/etcd/issues/17350).
|
||||
2. Raise a `kubernetes/org` pull request to temporarily elevate permissions for the GitHub release team.
|
||||
3. Once permissions are elevated, temporarily relax [branch protections](https://github.com/etcd-io/etcd/settings/branches) to allow pushing changes directly to `release-*` branches in GitHub.
|
||||
4. Verify you can pass the authentication to the image registries,
|
||||
2. Raise a `kubernetes/org` pull request ([example PR](https://github.com/kubernetes/org/pull/5582)) to ensure members of the release team are added to the [release github team](https://github.com/orgs/etcd-io/teams/release-etcd).
|
||||
|
||||
On the day of the release:
|
||||
|
||||
1. Verify you can pass the authentication to the image registries,
|
||||
- `docker login gcr.io`
|
||||
- `docker login quay.io`
|
||||
- If the release person doesn't have access to 1password, one of the owners (@ahrtr, @ivanvc, @jmhbnz, @serathius) needs to share the password with them per [this guide](https://support.1password.com/share-items/). See rough steps below,
|
||||
|
@ -80,45 +86,52 @@ which don't need to be executed before releasing each version.
|
|||
- Select `Password of quay.io`.
|
||||
- Click `Share` on the top right, and set expiration as `1 hour` and only available to the release person using his/her email.
|
||||
- Click `Copy Link` then send the link to the release person via slack or email.
|
||||
5. Clone the etcd repository and checkout the target branch,
|
||||
2. Clone the etcd repository and checkout the target branch,
|
||||
- `git clone --branch release-3.X git@github.com:etcd-io/etcd.git`
|
||||
6. Run the release script under the repository's root directory, replacing `${VERSION}` with a value without the `v` prefix, i.e. `3.5.13`.
|
||||
3. Run the release script under the repository's root directory, replacing `${VERSION}` with a value without the `v` prefix, i.e. `3.5.13`.
|
||||
- `DRY_RUN=false ./scripts/release.sh ${VERSION}`
|
||||
- **NOTE:** When doing a pre-release (i.e., a version from the main branch, 3.6.0-alpha.2), you will need to explicitly set the branch to main:
|
||||
```
|
||||
|
||||
```bash
|
||||
DRY_RUN=false BRANCH=main ./scripts/release.sh ${VERSION}
|
||||
```
|
||||
|
||||
It generates all release binaries under the directory `/tmp/etcd-release-${VERSION}/etcd/release/` and images. Binaries are pushed to the Google Cloud bucket
|
||||
under project `etcd-development`, and images are pushed to `quay.io` and `gcr.io`.
|
||||
7. Publish the release page on GitHub
|
||||
- It is advisable to do a dry run before the actual release. This will create a `/tmp` directory. Do **NOT** forget to remove this directory before the actual release.
|
||||
|
||||
```bash
|
||||
DRY_RUN=true BRANCH=${BRANCH} ./scripts/release.sh ${VERSION}
|
||||
```
|
||||
|
||||
4. Publish the release page on GitHub
|
||||
- Open the **draft** release URL shown by the release script
|
||||
- Click the pen button at the top right to edit the release
|
||||
- Review that it looks correct, reviewing that the bottom checkboxes are checked depending on the
|
||||
release version (v3.4 no checkboxes, v3.5 has the set as latest release checkbox checked,
|
||||
v3.6 has the set as pre-release checkbox checked)
|
||||
release version (v3.4 & v3.5 no checkboxes, v3.6 has the set as latest release checkbox checked,
|
||||
v3.7 has the set as pre-release checkbox checked)
|
||||
- Then, publish the release
|
||||
8. Announce to the etcd-dev googlegroup
|
||||
5. Announce to the etcd-dev googlegroup
|
||||
|
||||
Follow the format of previous release emails sent to etcd-dev@googlegroups.com, see an example below. After sending out the email, ask one of the mailing list maintainers to approve the email from the pending list. Additionally, label the release email as `Release`.
|
||||
|
||||
```text
|
||||
Hello,
|
||||
```text
|
||||
Hello,
|
||||
|
||||
etcd v3.4.30 is now public!
|
||||
etcd v3.4.30 is now public!
|
||||
|
||||
https://github.com/etcd-io/etcd/releases/tag/v3.4.30
|
||||
https://github.com/etcd-io/etcd/releases/tag/v3.4.30
|
||||
|
||||
Thanks to everyone who contributed to the release!
|
||||
Thanks to everyone who contributed to the release!
|
||||
|
||||
etcd team
|
||||
```
|
||||
etcd team
|
||||
```
|
||||
|
||||
9. Update the changelog to reflect the correct release date.
|
||||
10. Paste the release link to the issue raised in Step 1 and close the issue.
|
||||
11. Restore standard branch protection settings and raise a follow-up `kubernetes/org` pull request to return to least privilege permissions.
|
||||
12. Crease a new stable branch through `git push origin release-${VERSION_MAJOR}.${VERSION_MINOR}` if this is a new major or minor stable release.
|
||||
13. Re-generate a new password for quay.io if needed (e.g. shared to a contributor who isn't in the release team, and we should rotate the password at least once every 3 months).
|
||||
6. Update the changelog to reflect the correct release date.
|
||||
7. Paste the release link to the issue raised in Step 1 and close the issue.
|
||||
8. Raise a follow-up `kubernetes/org` pull request to return the GitHub release team to empty, least privilege state.
|
||||
9. Crease a new stable branch through `git push origin release-${VERSION_MAJOR}.${VERSION_MINOR}` if this is a new major or minor stable release.
|
||||
10. Re-generate a new password for quay.io if needed (e.g. shared to a contributor who isn't in the release team, and we should rotate the password at least once every 3 months).
|
||||
|
||||
#### Release known issues
|
||||
|
||||
|
|
|
@ -12,18 +12,18 @@ Each item has an assigned priority. Refer to [priority definitions](https://gith
|
|||
|
||||
For a full list of tasks in `v3.6.0`, please see [milestone etcd-v3.6](https://github.com/etcd-io/etcd/milestone/38).
|
||||
|
||||
| Title | Priority | Status | Note |
|
||||
|--------------------------------------------------------------------------------------------------------------------|----------|-------------|--------------------------------------------------------------------------------------------------------------|
|
||||
| [Support downgrade](https://github.com/etcd-io/etcd/issues/11716) | priority/important-soon | In progress | etcd will support downgrade starting from 3.6.0. But it will also support offline downgrade from 3.5 to 3.4. |
|
||||
| [StoreV2 deprecation](https://github.com/etcd-io/etcd/issues/12913) | priority/important-soon | In progress | This task will be covered in both 3.6 and 3.7. |
|
||||
| [Release raft 3.6.0](https://github.com/etcd-io/raft/issues/89) | priority/important-soon | Not started | etcd 3.6.0 will depends on raft 3.6.0 |
|
||||
| [Release bbolt 1.4.0](https://github.com/etcd-io/bbolt/issues/553) | priority/important-soon | Not started | etcd 3.6.0 will depends on bbolt 1.4.0 |
|
||||
| [Support /livez and /readyz endpoints](https://github.com/etcd-io/etcd/issues/16007) | priority/important-longterm | In progress | It provides clearer APIs, and can also work around the stalled writes issue |
|
||||
| Title | Priority | Status | Note |
|
||||
|--------------------------------------------------------------------------------------------------------------------|-----------------------------|-------------|--------------------------------------------------------------------------------------------------------------|
|
||||
| [Support downgrade](https://github.com/etcd-io/etcd/issues/11716) | priority/important-soon | In progress | etcd will support downgrade starting from 3.6.0. But it will also support offline downgrade from 3.5 to 3.4. |
|
||||
| [StoreV2 deprecation](https://github.com/etcd-io/etcd/issues/12913) | priority/important-soon | In progress | This task will be covered in both 3.6 and 3.7. |
|
||||
| [Release raft 3.6.0](https://github.com/etcd-io/raft/issues/89) | priority/important-soon | Completed | etcd 3.6.0 will depends on raft 3.6.0 |
|
||||
| [Release bbolt 1.4.0](https://github.com/etcd-io/bbolt/issues/553) | priority/important-soon | Completed | etcd 3.6.0 will depends on bbolt 1.4.0 |
|
||||
| [Support /livez and /readyz endpoints](https://github.com/etcd-io/etcd/issues/16007) | priority/important-longterm | Completed | It provides clearer APIs, and can also work around the stalled writes issue |
|
||||
| [Bump gRPC](https://github.com/etcd-io/etcd/issues/16290) | priority/important-longterm | Completed | It isn't guaranteed to be resolved in 3.6, and might be postponed to 3.7 depending on the effort and risk. |
|
||||
| [Deprecate grpc-gateway or bump it](https://github.com/etcd-io/etcd/issues/14499) | priority/important-longterm | Completed | It isn't guaranteed to be resolved in 3.6, and might be postponed to 3.7 depending on the effort and risk. |
|
||||
| [bbolt: Add logger into bbolt](https://github.com/etcd-io/bbolt/issues/509) | priority/important-longterm | Completed | It's important to diagnose bbolt issues |
|
||||
| [bbolt: Add surgery commands](https://github.com/etcd-io/bbolt/issues/370) | priority/important-longterm | Completed | Surgery commands are important for fixing corrupted db files |
|
||||
| [Evaluate and (Gradulate or deprecate/remove) experimental features](https://github.com/etcd-io/etcd/issues/16292) | priority/backlog | Not started | This task will be covered in both 3.6 and 3.7. |
|
||||
| [Evaluate and (Gradulate or deprecate/remove) experimental features](https://github.com/etcd-io/etcd/issues/16292) | priority/backlog | Not started | This task will be covered in both 3.6 and 3.7. |
|
||||
|
||||
## v3.7.0
|
||||
|
||||
|
@ -32,8 +32,9 @@ For a full list of tasks in `v3.7.0`, please see [milestone etcd-v3.7](https://g
|
|||
| Title | Priority | Note |
|
||||
|-------------------------------------------------------------------------------------------------------------------|----------|-----------------------------------------------------------------------------------|
|
||||
| [StoreV2 deprecation](https://github.com/etcd-io/etcd/issues/12913) | P0 | Finish the remaining tasks 3.7. |
|
||||
| [Support range stream](https://github.com/etcd-io/etcd/issues/12342) | P0 | to be investigated & discussed. |
|
||||
| [Refactor lease: Lease might be revoked by mistake by old leader](https://github.com/etcd-io/etcd/issues/15247) | P1 | to be investigated & discussed |
|
||||
| [Integrate raft's new feature (async write) into etcd](https://github.com/etcd-io/etcd/issues/16291) | P1 | It should improve the performance |
|
||||
| [Integrate raft's new feature (async write) into etcd](https://github.com/etcd-io/etcd/issues/16291) | P1 | It should improve the performance |
|
||||
| [bbolt: Support customizing the bbolt rebalance threshold](https://github.com/etcd-io/bbolt/issues/422) | P2 | It may get rid of etcd's defragmentation. Both bbolt and etcd need to be changed. |
|
||||
| [Evaluate and (graduate or deprecate/remove) experimental features](https://github.com/etcd-io/etcd/issues/16292) | P2 | Finish the remaining tasks 3.7. |
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ Following are a few example searches on PR for convenience:
|
|||
|
||||
## Scope
|
||||
|
||||
These guidelines serve as a primary document for managing PRs in `etcd`. Everyone is welcome to help manage PRs but the work and responsibilities discussed in this document are created with `etcd` maintainers and active contributors in mind.
|
||||
These guidelines serve as a primary document for managing PRs and review policy in `etcd`. Everyone is welcome to help manage PRs but the work and responsibilities discussed in this document are created with `etcd` maintainers and active contributors in mind.
|
||||
|
||||
## Ensure tests are run
|
||||
|
||||
|
@ -30,3 +30,32 @@ Reviewers are responsive in a timely fashion, but considering everyone is busy,
|
|||
## Verify important labels are in place
|
||||
|
||||
Make sure that appropriate reviewers are added to the PR. Also, make sure that a milestone is identified. If any of these or other important labels are missing, add them. If a correct label cannot be decided, leave a comment for the maintainers to do so as needed.
|
||||
|
||||
## Review policy
|
||||
|
||||
To ensure code quality and shared ownership, this review policy applies to all pull requests (PRs).
|
||||
|
||||
### Default rule
|
||||
|
||||
PRs should get at least two approvals (/lgtm or GitHub review approval) before merging.
|
||||
|
||||
Notes:
|
||||
|
||||
* Approvals should come from a maintainer, reviewer, or submodule owner familiar with the relevant code or area.
|
||||
* If there’s disagreement, maintainers should discuss and agree before merging.
|
||||
|
||||
### Exceptions for Less Impactful PRs
|
||||
|
||||
For low-risk changes — such as:
|
||||
|
||||
* CI workflows
|
||||
* Documentation
|
||||
* Comments
|
||||
|
||||
The rule can be relaxed:
|
||||
|
||||
* One approval is generally enough.
|
||||
|
||||
However:
|
||||
|
||||
* If the author is a maintainer, they should still get approval from another maintainer, reviewer, or submodule owner, even for minor changes.
|
||||
|
|
|
@ -2135,6 +2135,19 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"etcdserverpbDowngradeInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "enabled indicates whether the cluster is enabled to downgrade."
|
||||
},
|
||||
"targetVersion": {
|
||||
"type": "string",
|
||||
"description": "targetVersion is the target downgrade version."
|
||||
}
|
||||
}
|
||||
},
|
||||
"etcdserverpbDowngradeRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -2689,7 +2702,7 @@
|
|||
"count": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "count is set to the number of keys within the range when requested."
|
||||
"description": "count is set to the actual number of keys within the range when requested.\nUnlike Kvs, it is unaffected by limits and filters (e.g., Min/Max, Create/Modify, Revisions)\nand reflects the full count within the specified range."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -2834,12 +2847,16 @@
|
|||
},
|
||||
"storageVersion": {
|
||||
"type": "string",
|
||||
"description": "storageVersion is the version of the db file. It might be get updated with delay in relationship to the target cluster version."
|
||||
"description": "storageVersion is the version of the db file. It might be updated with delay in relationship to the target cluster version."
|
||||
},
|
||||
"dbSizeQuota": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"title": "dbSizeQuota is the configured etcd storage quota in bytes (the value passed to etcd instance by flag --quota-backend-bytes)"
|
||||
},
|
||||
"downgradeInfo": {
|
||||
"$ref": "#/definitions/etcdserverpbDowngradeInfo",
|
||||
"description": "downgradeInfo indicates if there is downgrade process."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -318,7 +318,7 @@
|
|||
"name": {
|
||||
"type": "string",
|
||||
"format": "byte",
|
||||
"description": "name is the election identifier that correponds to the leadership key."
|
||||
"description": "name is the election identifier that corresponds to the leadership key."
|
||||
},
|
||||
"key": {
|
||||
"type": "string",
|
||||
|
|
|
@ -36,7 +36,12 @@ weeks inactive voting period and as long as two maintainers are on board.
|
|||
|
||||
Changes in project governance could be initiated by opening a GitHub PR.
|
||||
|
||||
## SIG-etcd Governance
|
||||
|
||||
[SIG-etcd Governance] is documented in the Kubernetes/community repository.
|
||||
|
||||
[community membership]: /Documentation/contributor-guide/community-membership.md
|
||||
[Code of Conduct]: /code-of-conduct.md
|
||||
[contributor guide]: /CONTRIBUTING.md
|
||||
[maintainers]: /OWNERS
|
||||
[SIG-etcd Governance]: https://github.com/kubernetes/community/blob/master/sig-etcd/charter.md#deviations-from-sig-governance
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -187,7 +187,7 @@
|
|||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
Copyright 2013 The etcd Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
24
Makefile
24
Makefile
|
@ -1,6 +1,8 @@
|
|||
REPOSITORY_ROOT := $(shell git rev-parse --show-toplevel)
|
||||
|
||||
.PHONY: all
|
||||
all: build
|
||||
include tests/robustness/makefile.mk
|
||||
include $(REPOSITORY_ROOT)/tests/robustness/Makefile
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
|
@ -54,10 +56,26 @@ test-grpcproxy-e2e: build
|
|||
test-e2e-release: build
|
||||
PASSES="release e2e" ./scripts/test.sh $(GO_TEST_FLAGS)
|
||||
|
||||
# When we release the first 3.7.0-alpha.0, we can remove `VERSION="3.7.99"` below.
|
||||
.PHONY: test-release
|
||||
test-release:
|
||||
PASSES="release_tests" VERSION="3.7.99" ./scripts/test.sh $(GO_TEST_FLAGS)
|
||||
|
||||
.PHONY: test-robustness
|
||||
test-robustness:
|
||||
PASSES="robustness" ./scripts/test.sh $(GO_TEST_FLAGS)
|
||||
|
||||
.PHONY: test-coverage
|
||||
test-coverage:
|
||||
COVERDIR=covdir PASSES="build cov" ./scripts/test.sh $(GO_TEST_FLAGS)
|
||||
|
||||
.PHONY: upload-coverage-report
|
||||
upload-coverage-report:
|
||||
return_code=0; \
|
||||
$(MAKE) test-coverage || return_code=$$?; \
|
||||
COVERDIR=covdir ./scripts/codecov_upload.sh; \
|
||||
exit $$return_code
|
||||
|
||||
.PHONY: fuzz
|
||||
fuzz:
|
||||
./scripts/fuzzing.sh
|
||||
|
@ -210,3 +228,7 @@ verify-go-versions:
|
|||
.PHONY: sync-toolchain-directive
|
||||
sync-toolchain-directive:
|
||||
./scripts/sync_go_toolchain_directive.sh
|
||||
|
||||
.PHONY: markdown-diff-lint
|
||||
markdown-diff-lint:
|
||||
./scripts/markdown_diff_lint.sh
|
||||
|
|
16
OWNERS
16
OWNERS
|
@ -2,11 +2,23 @@
|
|||
|
||||
approvers:
|
||||
- ahrtr # Benjamin Wang <benjamin.ahrtr@gmail.com> <benjamin.wang@broadcom.com>
|
||||
- fuweid # Wei Fu <fuweid89@gmail.com>
|
||||
- jmhbnz # James Blair <jablair@redhat.com> <mail@jamesblair.net>
|
||||
- serathius # Marek Siarkowicz <siarkowicz@google.com> <marek.siarkowicz@gmail.com>
|
||||
- spzala # Sahdev Zala <spzala@us.ibm.com>
|
||||
- wenjiaswe # Wenjia Zhang <wenjiazhang@google.com> <wenjia.swe@gmail.com>
|
||||
reviewers:
|
||||
- fuweid # Wei Fu <fuweid89@gmail.com>
|
||||
- ivanvc # Ivan Valdes <ivan@vald.es>
|
||||
- siyuanfoundation # Siyuan Zhang <sizhang@google.com> <physicsbug@gmail.com>
|
||||
emeritus_approvers:
|
||||
- bdarnell # Ben Darnell <ben@bendarnell.com>
|
||||
- fanminshi # Fanmin Shi <fanmin.shi@gmail.com>
|
||||
- gyuho # Gyuho Lee <gyuhox@gmail.com>
|
||||
- hexfusion # Sam Batschelet <sbatsche@redhat.com>
|
||||
- heyitsanthony # Anthony Romano <romanoanthony061@gmail.com>
|
||||
- jingyih # Jingyi Hu <jingyih@google.com>
|
||||
- jpbetz # Joe Betz <jpbetz@google.com>
|
||||
- mitake # Hitoshi Mitake <h.mitake@gmail.com>
|
||||
- philips # Brandon Philips <brandon@ifup.org>
|
||||
- ptabor # Piotr Tabor <piotr.tabor@gmail.com>
|
||||
- wenjiaswe # Wenjia Zhang <wenjiazhang@google.com> <wenjia.swe@gmail.com>
|
||||
- xiang90 # Xiang Li <xiangli.cs@gmail.com>
|
||||
|
|
28
README.md
28
README.md
|
@ -5,7 +5,7 @@
|
|||
[](https://github.com/etcd-io/etcd/actions/workflows/tests.yaml)
|
||||
[](https://github.com/etcd-io/etcd/actions/workflows/codeql-analysis.yml)
|
||||
[](https://etcd.io/docs)
|
||||
[](https://godoc.org/github.com/etcd-io/etcd)
|
||||
[](https://godocs.io/go.etcd.io/etcd/v3)
|
||||
[](https://github.com/etcd-io/etcd/releases)
|
||||
[](https://github.com/etcd-io/etcd/blob/main/LICENSE)
|
||||
[](https://scorecard.dev/viewer/?uri=github.com/etcd-io/etcd)
|
||||
|
@ -42,6 +42,18 @@ See [etcdctl][etcdctl] for a simple command line client.
|
|||
[vulcand]: https://github.com/vulcand/vulcand
|
||||
[etcdctl]: https://github.com/etcd-io/etcd/tree/main/etcdctl
|
||||
|
||||
## Documentation
|
||||
|
||||
The most common API documentation you'll need can be found here:
|
||||
|
||||
* [go.etcd.io/etcd/api/v3](https://godocs.io/go.etcd.io/etcd/api/v3)
|
||||
* [go.etcd.io/etcd/client/pkg/v3](https://godocs.io/go.etcd.io/etcd/client/pkg/v3)
|
||||
* [go.etcd.io/etcd/client/v3](https://godocs.io/go.etcd.io/etcd/client/v3)
|
||||
* [go.etcd.io/etcd/etcdctl/v3](https://godocs.io/go.etcd.io/etcd/etcdctl/v3)
|
||||
* [go.etcd.io/etcd/pkg/v3](https://godocs.io/go.etcd.io/etcd/pkg/v3)
|
||||
* [go.etcd.io/etcd/raft/v3](https://godocs.io/go.etcd.io/etcd/raft/v3)
|
||||
* [go.etcd.io/etcd/server/v3](https://godocs.io/go.etcd.io/etcd/server/v3)
|
||||
|
||||
## Maintainers
|
||||
|
||||
[Maintainers](OWNERS) strive to shape an inclusive open source project culture where users are heard and contributors feel respected and empowered. Maintainers aim to build productive relationships across different companies and disciplines. Read more about [Maintainers role and responsibilities](Documentation/contributor-guide/community-membership.md#maintainers).
|
||||
|
@ -186,19 +198,7 @@ See [PR management](https://github.com/etcd-io/etcd/blob/main/Documentation/cont
|
|||
|
||||
## etcd Emeritus Maintainers
|
||||
|
||||
These emeritus maintainers dedicated a part of their career to etcd and reviewed code, triaged bugs and pushed the project forward over a substantial period of time. Their contribution is greatly appreciated.
|
||||
|
||||
* Fanmin Shi
|
||||
* Anthony Romano
|
||||
* Brandon Philips
|
||||
* Joe Betz
|
||||
* Gyuho Lee
|
||||
* Jingyi Hu
|
||||
* Xiang Li
|
||||
* Ben Darnell
|
||||
* Sam Batschelet
|
||||
* Piotr Tabor
|
||||
* Hitoshi Mitake
|
||||
etcd [emeritus maintainers](OWNERS) dedicated a part of their career to etcd and reviewed code, triaged bugs and pushed the project forward over a substantial period of time. Their contribution is greatly appreciated.
|
||||
|
||||
### License
|
||||
|
||||
|
|
|
@ -187,7 +187,7 @@
|
|||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
Copyright 2020 The etcd Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -46,6 +46,9 @@ func request_KV_Range_0(ctx context.Context, marshaler runtime.Marshaler, client
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.Range(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -70,6 +73,9 @@ func request_KV_Put_0(ctx context.Context, marshaler runtime.Marshaler, client e
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.Put(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -94,6 +100,9 @@ func request_KV_DeleteRange_0(ctx context.Context, marshaler runtime.Marshaler,
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.DeleteRange(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -118,6 +127,9 @@ func request_KV_Txn_0(ctx context.Context, marshaler runtime.Marshaler, client e
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.Txn(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -142,6 +154,9 @@ func request_KV_Compact_0(ctx context.Context, marshaler runtime.Marshaler, clie
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.Compact(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -158,14 +173,12 @@ func local_request_KV_Compact_0(ctx context.Context, marshaler runtime.Marshaler
|
|||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
||||
func request_Watch_Watch_0(ctx context.Context, marshaler runtime.Marshaler, client etcdserverpb.WatchClient, req *http.Request, pathParams map[string]string) (etcdserverpb.Watch_WatchClient, runtime.ServerMetadata, chan error, error) {
|
||||
func request_Watch_Watch_0(ctx context.Context, marshaler runtime.Marshaler, client etcdserverpb.WatchClient, req *http.Request, pathParams map[string]string) (etcdserverpb.Watch_WatchClient, runtime.ServerMetadata, error) {
|
||||
var metadata runtime.ServerMetadata
|
||||
errChan := make(chan error, 1)
|
||||
stream, err := client.Watch(ctx)
|
||||
if err != nil {
|
||||
grpclog.Errorf("Failed to start streaming: %v", err)
|
||||
close(errChan)
|
||||
return nil, metadata, errChan, err
|
||||
return nil, metadata, err
|
||||
}
|
||||
dec := marshaler.NewDecoder(req.Body)
|
||||
handleSend := func() error {
|
||||
|
@ -185,10 +198,8 @@ func request_Watch_Watch_0(ctx context.Context, marshaler runtime.Marshaler, cli
|
|||
return nil
|
||||
}
|
||||
go func() {
|
||||
defer close(errChan)
|
||||
for {
|
||||
if err := handleSend(); err != nil {
|
||||
errChan <- err
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -199,10 +210,10 @@ func request_Watch_Watch_0(ctx context.Context, marshaler runtime.Marshaler, cli
|
|||
header, err := stream.Header()
|
||||
if err != nil {
|
||||
grpclog.Errorf("Failed to get header from client: %v", err)
|
||||
return nil, metadata, errChan, err
|
||||
return nil, metadata, err
|
||||
}
|
||||
metadata.HeaderMD = header
|
||||
return stream, metadata, errChan, nil
|
||||
return stream, metadata, nil
|
||||
}
|
||||
|
||||
func request_Lease_LeaseGrant_0(ctx context.Context, marshaler runtime.Marshaler, client etcdserverpb.LeaseClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
|
@ -213,6 +224,9 @@ func request_Lease_LeaseGrant_0(ctx context.Context, marshaler runtime.Marshaler
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.LeaseGrant(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -237,6 +251,9 @@ func request_Lease_LeaseRevoke_0(ctx context.Context, marshaler runtime.Marshale
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.LeaseRevoke(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -261,6 +278,9 @@ func request_Lease_LeaseRevoke_1(ctx context.Context, marshaler runtime.Marshale
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.LeaseRevoke(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -277,14 +297,12 @@ func local_request_Lease_LeaseRevoke_1(ctx context.Context, marshaler runtime.Ma
|
|||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
||||
func request_Lease_LeaseKeepAlive_0(ctx context.Context, marshaler runtime.Marshaler, client etcdserverpb.LeaseClient, req *http.Request, pathParams map[string]string) (etcdserverpb.Lease_LeaseKeepAliveClient, runtime.ServerMetadata, chan error, error) {
|
||||
func request_Lease_LeaseKeepAlive_0(ctx context.Context, marshaler runtime.Marshaler, client etcdserverpb.LeaseClient, req *http.Request, pathParams map[string]string) (etcdserverpb.Lease_LeaseKeepAliveClient, runtime.ServerMetadata, error) {
|
||||
var metadata runtime.ServerMetadata
|
||||
errChan := make(chan error, 1)
|
||||
stream, err := client.LeaseKeepAlive(ctx)
|
||||
if err != nil {
|
||||
grpclog.Errorf("Failed to start streaming: %v", err)
|
||||
close(errChan)
|
||||
return nil, metadata, errChan, err
|
||||
return nil, metadata, err
|
||||
}
|
||||
dec := marshaler.NewDecoder(req.Body)
|
||||
handleSend := func() error {
|
||||
|
@ -304,10 +322,8 @@ func request_Lease_LeaseKeepAlive_0(ctx context.Context, marshaler runtime.Marsh
|
|||
return nil
|
||||
}
|
||||
go func() {
|
||||
defer close(errChan)
|
||||
for {
|
||||
if err := handleSend(); err != nil {
|
||||
errChan <- err
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -318,10 +334,10 @@ func request_Lease_LeaseKeepAlive_0(ctx context.Context, marshaler runtime.Marsh
|
|||
header, err := stream.Header()
|
||||
if err != nil {
|
||||
grpclog.Errorf("Failed to get header from client: %v", err)
|
||||
return nil, metadata, errChan, err
|
||||
return nil, metadata, err
|
||||
}
|
||||
metadata.HeaderMD = header
|
||||
return stream, metadata, errChan, nil
|
||||
return stream, metadata, nil
|
||||
}
|
||||
|
||||
func request_Lease_LeaseTimeToLive_0(ctx context.Context, marshaler runtime.Marshaler, client etcdserverpb.LeaseClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||
|
@ -332,6 +348,9 @@ func request_Lease_LeaseTimeToLive_0(ctx context.Context, marshaler runtime.Mars
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.LeaseTimeToLive(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -356,6 +375,9 @@ func request_Lease_LeaseTimeToLive_1(ctx context.Context, marshaler runtime.Mars
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.LeaseTimeToLive(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -380,6 +402,9 @@ func request_Lease_LeaseLeases_0(ctx context.Context, marshaler runtime.Marshale
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.LeaseLeases(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -404,6 +429,9 @@ func request_Lease_LeaseLeases_1(ctx context.Context, marshaler runtime.Marshale
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.LeaseLeases(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -428,6 +456,9 @@ func request_Cluster_MemberAdd_0(ctx context.Context, marshaler runtime.Marshale
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.MemberAdd(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -452,6 +483,9 @@ func request_Cluster_MemberRemove_0(ctx context.Context, marshaler runtime.Marsh
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.MemberRemove(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -476,6 +510,9 @@ func request_Cluster_MemberUpdate_0(ctx context.Context, marshaler runtime.Marsh
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.MemberUpdate(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -500,6 +537,9 @@ func request_Cluster_MemberList_0(ctx context.Context, marshaler runtime.Marshal
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.MemberList(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -524,6 +564,9 @@ func request_Cluster_MemberPromote_0(ctx context.Context, marshaler runtime.Mars
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.MemberPromote(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -548,6 +591,9 @@ func request_Maintenance_Alarm_0(ctx context.Context, marshaler runtime.Marshale
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.Alarm(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -572,6 +618,9 @@ func request_Maintenance_Status_0(ctx context.Context, marshaler runtime.Marshal
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.Status(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -596,6 +645,9 @@ func request_Maintenance_Defragment_0(ctx context.Context, marshaler runtime.Mar
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.Defragment(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -620,6 +672,9 @@ func request_Maintenance_Hash_0(ctx context.Context, marshaler runtime.Marshaler
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.Hash(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -644,6 +699,9 @@ func request_Maintenance_HashKV_0(ctx context.Context, marshaler runtime.Marshal
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.HashKV(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -668,6 +726,9 @@ func request_Maintenance_Snapshot_0(ctx context.Context, marshaler runtime.Marsh
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
stream, err := client.Snapshot(ctx, &protoReq)
|
||||
if err != nil {
|
||||
return nil, metadata, err
|
||||
|
@ -688,6 +749,9 @@ func request_Maintenance_MoveLeader_0(ctx context.Context, marshaler runtime.Mar
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.MoveLeader(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -712,6 +776,9 @@ func request_Maintenance_Downgrade_0(ctx context.Context, marshaler runtime.Mars
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.Downgrade(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -736,6 +803,9 @@ func request_Auth_AuthEnable_0(ctx context.Context, marshaler runtime.Marshaler,
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.AuthEnable(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -760,6 +830,9 @@ func request_Auth_AuthDisable_0(ctx context.Context, marshaler runtime.Marshaler
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.AuthDisable(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -784,6 +857,9 @@ func request_Auth_AuthStatus_0(ctx context.Context, marshaler runtime.Marshaler,
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.AuthStatus(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -808,6 +884,9 @@ func request_Auth_Authenticate_0(ctx context.Context, marshaler runtime.Marshale
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.Authenticate(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -832,6 +911,9 @@ func request_Auth_UserAdd_0(ctx context.Context, marshaler runtime.Marshaler, cl
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.UserAdd(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -856,6 +938,9 @@ func request_Auth_UserGet_0(ctx context.Context, marshaler runtime.Marshaler, cl
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.UserGet(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -880,6 +965,9 @@ func request_Auth_UserList_0(ctx context.Context, marshaler runtime.Marshaler, c
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.UserList(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -904,6 +992,9 @@ func request_Auth_UserDelete_0(ctx context.Context, marshaler runtime.Marshaler,
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.UserDelete(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -928,6 +1019,9 @@ func request_Auth_UserChangePassword_0(ctx context.Context, marshaler runtime.Ma
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.UserChangePassword(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -952,6 +1046,9 @@ func request_Auth_UserGrantRole_0(ctx context.Context, marshaler runtime.Marshal
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.UserGrantRole(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -976,6 +1073,9 @@ func request_Auth_UserRevokeRole_0(ctx context.Context, marshaler runtime.Marsha
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.UserRevokeRole(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -1000,6 +1100,9 @@ func request_Auth_RoleAdd_0(ctx context.Context, marshaler runtime.Marshaler, cl
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.RoleAdd(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -1024,6 +1127,9 @@ func request_Auth_RoleGet_0(ctx context.Context, marshaler runtime.Marshaler, cl
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.RoleGet(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -1048,6 +1154,9 @@ func request_Auth_RoleList_0(ctx context.Context, marshaler runtime.Marshaler, c
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.RoleList(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -1072,6 +1181,9 @@ func request_Auth_RoleDelete_0(ctx context.Context, marshaler runtime.Marshaler,
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.RoleDelete(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -1096,6 +1208,9 @@ func request_Auth_RoleGrantPermission_0(ctx context.Context, marshaler runtime.M
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.RoleGrantPermission(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -1120,6 +1235,9 @@ func request_Auth_RoleRevokePermission_0(ctx context.Context, marshaler runtime.
|
|||
if err := marshaler.NewDecoder(req.Body).Decode(protov1.MessageV2(&protoReq)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
if req.Body != nil {
|
||||
_, _ = io.Copy(io.Discard, req.Body)
|
||||
}
|
||||
msg, err := client.RoleRevokePermission(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||
return protov1.MessageV2(msg), metadata, err
|
||||
}
|
||||
|
@ -2221,20 +2339,12 @@ func RegisterWatchHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie
|
|||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, md, reqErrChan, err := request_Watch_Watch_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
||||
resp, md, err := request_Watch_Watch_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
for err := range reqErrChan {
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
runtime.HTTPStreamError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
forward_Watch_Watch_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) {
|
||||
m1, err := resp.Recv()
|
||||
return protov1.MessageV2(m1), err
|
||||
|
@ -2347,20 +2457,12 @@ func RegisterLeaseHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie
|
|||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, md, reqErrChan, err := request_Lease_LeaseKeepAlive_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
||||
resp, md, err := request_Lease_LeaseKeepAlive_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||
if err != nil {
|
||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
for err := range reqErrChan {
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
runtime.HTTPStreamError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
forward_Lease_LeaseKeepAlive_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) {
|
||||
m1, err := resp.Recv()
|
||||
return protov1.MessageV2(m1), err
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -507,7 +507,9 @@ message RangeResponse {
|
|||
repeated mvccpb.KeyValue kvs = 2;
|
||||
// more indicates if there are more keys to return in the requested range.
|
||||
bool more = 3;
|
||||
// count is set to the number of keys within the range when requested.
|
||||
// count is set to the actual number of keys within the range when requested.
|
||||
// Unlike Kvs, it is unaffected by limits and filters (e.g., Min/Max, Create/Modify, Revisions)
|
||||
// and reflects the full count within the specified range.
|
||||
int64 count = 4;
|
||||
}
|
||||
|
||||
|
@ -1194,10 +1196,19 @@ message StatusResponse {
|
|||
int64 dbSizeInUse = 9 [(versionpb.etcd_version_field)="3.4"];
|
||||
// isLearner indicates if the member is raft learner.
|
||||
bool isLearner = 10 [(versionpb.etcd_version_field)="3.4"];
|
||||
// storageVersion is the version of the db file. It might be get updated with delay in relationship to the target cluster version.
|
||||
// storageVersion is the version of the db file. It might be updated with delay in relationship to the target cluster version.
|
||||
string storageVersion = 11 [(versionpb.etcd_version_field)="3.6"];
|
||||
// dbSizeQuota is the configured etcd storage quota in bytes (the value passed to etcd instance by flag --quota-backend-bytes)
|
||||
int64 dbSizeQuota = 12 [(versionpb.etcd_version_field)="3.6"];
|
||||
// downgradeInfo indicates if there is downgrade process.
|
||||
DowngradeInfo downgradeInfo = 13 [(versionpb.etcd_version_field)="3.6"];
|
||||
}
|
||||
|
||||
message DowngradeInfo {
|
||||
// enabled indicates whether the cluster is enabled to downgrade.
|
||||
bool enabled = 1;
|
||||
// targetVersion is the target downgrade version.
|
||||
string targetVersion = 2;
|
||||
}
|
||||
|
||||
message AuthEnableRequest {
|
||||
|
|
30
api/go.mod
30
api/go.mod
|
@ -1,30 +1,30 @@
|
|||
module go.etcd.io/etcd/api/v3
|
||||
|
||||
go 1.23
|
||||
go 1.24
|
||||
|
||||
toolchain go1.23.6
|
||||
toolchain go1.24.4
|
||||
|
||||
require (
|
||||
github.com/coreos/go-semver v0.3.1
|
||||
github.com/gogo/protobuf v1.3.2
|
||||
github.com/golang/protobuf v1.5.4
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f
|
||||
google.golang.org/grpc v1.70.0
|
||||
google.golang.org/protobuf v1.36.4
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822
|
||||
google.golang.org/grpc v1.73.0
|
||||
google.golang.org/protobuf v1.36.6
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
go.opentelemetry.io/otel v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.34.0 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
go.opentelemetry.io/otel v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect
|
||||
golang.org/x/net v0.41.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
|
|
72
api/go.sum
72
api/go.sum
|
@ -1,47 +1,47 @@
|
|||
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
||||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||
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/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 h1:VD1gqscl4nYs1YxVuSdemTrSgTKrwOWDK0FVFMqm+Cg=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0/go.mod h1:4EgsQoS4TOhJizV+JTFg40qx1Ofh3XmXEQNBpgvNT40=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
|
||||
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/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
||||
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
||||
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
|
||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
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=
|
||||
|
@ -51,20 +51,20 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
|
@ -73,14 +73,14 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
|||
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-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
|
||||
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
|
||||
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
|
|
|
@ -212,7 +212,7 @@ var (
|
|||
ErrInvalidAuthMgmt = Error(ErrGRPCInvalidAuthMgmt)
|
||||
ErrClusterIDMismatch = Error(ErrGRPCClusterIDMismatch)
|
||||
//revive:disable:var-naming
|
||||
// Deprecated: Please use ErrGRPCClusterIDMismatch.
|
||||
// Deprecated: Please use ErrClusterIDMismatch.
|
||||
ErrClusterIdMismatch = ErrClusterIDMismatch
|
||||
//revive:enable:var-naming
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ import (
|
|||
var (
|
||||
// MinClusterVersion is the min cluster version this etcd binary is compatible with.
|
||||
MinClusterVersion = "3.0.0"
|
||||
Version = "3.6.0-alpha.0"
|
||||
Version = "3.7.0-alpha.0"
|
||||
APIVersion = "unknown"
|
||||
|
||||
// Git SHA Value will be set during build
|
||||
|
@ -43,6 +43,7 @@ var (
|
|||
V3_5 = semver.Version{Major: 3, Minor: 5}
|
||||
V3_6 = semver.Version{Major: 3, Minor: 6}
|
||||
V3_7 = semver.Version{Major: 3, Minor: 7}
|
||||
V3_8 = semver.Version{Major: 3, Minor: 8}
|
||||
V4_0 = semver.Version{Major: 4, Minor: 0}
|
||||
|
||||
// AllVersions keeps all the versions in ascending order.
|
||||
|
|
|
@ -10,6 +10,15 @@
|
|||
},
|
||||
{
|
||||
"project": "github.com/anishathalye/porcupine",
|
||||
"licenses": [
|
||||
{
|
||||
"type": "MIT License",
|
||||
"confidence": 0.96875
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"project": "github.com/antithesishq/antithesis-sdk-go",
|
||||
"licenses": [
|
||||
{
|
||||
"type": "MIT License",
|
||||
|
@ -36,7 +45,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"project": "github.com/cenkalti/backoff/v4",
|
||||
"project": "github.com/cenkalti/backoff/v5",
|
||||
"licenses": [
|
||||
{
|
||||
"type": "MIT License",
|
||||
|
@ -260,33 +269,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"project": "github.com/klauspost/compress",
|
||||
"licenses": [
|
||||
{
|
||||
"type": "Apache License 2.0",
|
||||
"confidence": 0.9376299376299376
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"project": "github.com/klauspost/compress/internal/snapref",
|
||||
"licenses": [
|
||||
{
|
||||
"type": "BSD 3-clause \"New\" or \"Revised\" License",
|
||||
"confidence": 0.9663865546218487
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"project": "github.com/klauspost/compress/zstd/internal/xxhash",
|
||||
"licenses": [
|
||||
{
|
||||
"type": "MIT License",
|
||||
"confidence": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"project": "github.com/mattn/go-colorable",
|
||||
"licenses": [
|
||||
|
@ -323,6 +305,24 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"project": "github.com/olekukonko/errors",
|
||||
"licenses": [
|
||||
{
|
||||
"type": "MIT License",
|
||||
"confidence": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"project": "github.com/olekukonko/ll",
|
||||
"licenses": [
|
||||
{
|
||||
"type": "MIT License",
|
||||
"confidence": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"project": "github.com/olekukonko/tablewriter",
|
||||
"licenses": [
|
||||
|
@ -472,7 +472,7 @@
|
|||
"licenses": [
|
||||
{
|
||||
"type": "Apache License 2.0",
|
||||
"confidence": 1
|
||||
"confidence": 0.9988925802879292
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -481,16 +481,7 @@
|
|||
"licenses": [
|
||||
{
|
||||
"type": "Apache License 2.0",
|
||||
"confidence": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"project": "go.etcd.io/etcd/client/v2",
|
||||
"licenses": [
|
||||
{
|
||||
"type": "Apache License 2.0",
|
||||
"confidence": 1
|
||||
"confidence": 0.9988925802879292
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -499,7 +490,7 @@
|
|||
"licenses": [
|
||||
{
|
||||
"type": "Apache License 2.0",
|
||||
"confidence": 1
|
||||
"confidence": 0.9988925802879292
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -508,7 +499,7 @@
|
|||
"licenses": [
|
||||
{
|
||||
"type": "Apache License 2.0",
|
||||
"confidence": 1
|
||||
"confidence": 0.9988925802879292
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -517,7 +508,7 @@
|
|||
"licenses": [
|
||||
{
|
||||
"type": "Apache License 2.0",
|
||||
"confidence": 1
|
||||
"confidence": 0.9988925802879292
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -526,7 +517,7 @@
|
|||
"licenses": [
|
||||
{
|
||||
"type": "Apache License 2.0",
|
||||
"confidence": 1
|
||||
"confidence": 0.9988925802879292
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -535,7 +526,7 @@
|
|||
"licenses": [
|
||||
{
|
||||
"type": "Apache License 2.0",
|
||||
"confidence": 1
|
||||
"confidence": 0.9988925802879292
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -544,7 +535,7 @@
|
|||
"licenses": [
|
||||
{
|
||||
"type": "Apache License 2.0",
|
||||
"confidence": 1
|
||||
"confidence": 0.9988925802879292
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -553,7 +544,7 @@
|
|||
"licenses": [
|
||||
{
|
||||
"type": "Apache License 2.0",
|
||||
"confidence": 1
|
||||
"confidence": 0.9988925802879292
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -674,6 +665,19 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"project": "go.yaml.in/yaml/v2",
|
||||
"licenses": [
|
||||
{
|
||||
"type": "Apache License 2.0",
|
||||
"confidence": 1
|
||||
},
|
||||
{
|
||||
"type": "MIT License",
|
||||
"confidence": 0.8975609756097561
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"project": "golang.org/x/crypto",
|
||||
"licenses": [
|
||||
|
@ -782,15 +786,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"project": "sigs.k8s.io/json",
|
||||
"licenses": [
|
||||
{
|
||||
"type": "Apache License 2.0",
|
||||
"confidence": 0.9617021276595744
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"project": "sigs.k8s.io/yaml",
|
||||
"licenses": [
|
||||
|
@ -803,18 +798,5 @@
|
|||
"confidence": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"project": "sigs.k8s.io/yaml/goyaml.v2",
|
||||
"licenses": [
|
||||
{
|
||||
"type": "Apache License 2.0",
|
||||
"confidence": 1
|
||||
},
|
||||
{
|
||||
"type": "MIT License",
|
||||
"confidence": 0.8975609756097561
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -187,7 +187,7 @@
|
|||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
Copyright 2020 The etcd Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
|
@ -0,0 +1,4 @@
|
|||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
labels:
|
||||
- area/cache
|
|
@ -0,0 +1,3 @@
|
|||
# etcd cache
|
||||
|
||||
Experimental etcd client cache library.
|
|
@ -0,0 +1,228 @@
|
|||
// Copyright 2025 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
rpctypes "go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
)
|
||||
|
||||
// TODO: add gap-free replay for arbitrary startRevs and drop this guard.
|
||||
var ErrUnsupportedWatch = errors.New("cache: unsupported watch parameters")
|
||||
|
||||
// Cache buffers a single etcd Watch for a given key‐prefix and fan‑outs local watchers.
|
||||
type Cache struct {
|
||||
prefix string // prefix is the key-prefix this shard is responsible for ("" = root).
|
||||
cfg Config // immutable runtime configuration
|
||||
watcher clientv3.Watcher
|
||||
demux *demux // demux fans incoming events out to active watchers and manages resync.
|
||||
ready chan struct{}
|
||||
stop context.CancelFunc
|
||||
waitGroup sync.WaitGroup
|
||||
internalCtx context.Context
|
||||
}
|
||||
|
||||
// watchCtx collects all the knobs that both serveWatchEvents and watchRetryLoop need.
|
||||
type watchCtx struct {
|
||||
cache *Cache
|
||||
backoffStart time.Duration
|
||||
backoffMax time.Duration
|
||||
onFirstResponse func() // callback to fire once on first upstream response
|
||||
}
|
||||
|
||||
// New builds a cache shard that watches only the requested prefix.
|
||||
// For the root cache pass "".
|
||||
func New(watcher clientv3.Watcher, prefix string, opts ...Option) (*Cache, error) {
|
||||
cfg := defaultConfig()
|
||||
for _, opt := range opts {
|
||||
opt(&cfg)
|
||||
}
|
||||
|
||||
if cfg.HistoryWindowSize <= 0 {
|
||||
return nil, fmt.Errorf("invalid HistoryWindowSize %d (must be > 0)", cfg.HistoryWindowSize)
|
||||
}
|
||||
|
||||
internalCtx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
cache := &Cache{
|
||||
prefix: prefix,
|
||||
cfg: cfg,
|
||||
watcher: watcher,
|
||||
ready: make(chan struct{}),
|
||||
stop: cancel,
|
||||
internalCtx: internalCtx,
|
||||
}
|
||||
|
||||
cache.demux = NewDemux(internalCtx, &cache.waitGroup, cfg.HistoryWindowSize, cfg.ResyncInterval)
|
||||
|
||||
cache.waitGroup.Add(1)
|
||||
go func() {
|
||||
defer cache.waitGroup.Done()
|
||||
readyOnce := sync.Once{}
|
||||
|
||||
watchCtx := &watchCtx{
|
||||
cache: cache,
|
||||
backoffStart: cfg.InitialBackoff,
|
||||
backoffMax: cfg.MaxBackoff,
|
||||
onFirstResponse: func() { readyOnce.Do(func() { close(cache.ready) }) },
|
||||
}
|
||||
serveWatchEvents(internalCtx, watchCtx)
|
||||
}()
|
||||
|
||||
return cache, nil
|
||||
}
|
||||
|
||||
// Watch registers a cache-backed watcher for a given key or prefix.
|
||||
// It returns a WatchChan that streams WatchResponses containing events.
|
||||
func (c *Cache) Watch(ctx context.Context, key string, opts ...clientv3.OpOption) clientv3.WatchChan {
|
||||
select {
|
||||
case <-c.ready:
|
||||
case <-ctx.Done():
|
||||
emptyWatchChan := make(chan clientv3.WatchResponse)
|
||||
close(emptyWatchChan)
|
||||
return emptyWatchChan
|
||||
}
|
||||
|
||||
op := clientv3.OpGet(key, opts...)
|
||||
startRev := op.Rev()
|
||||
|
||||
// TODO: Support watch on subprefix and single key & arbitrary startRev support once we guarantee gap-free replay.
|
||||
if key != c.prefix || !clientv3.IsOptsWithPrefix(opts) || startRev != 0 {
|
||||
responseChan := make(chan clientv3.WatchResponse, 1)
|
||||
responseChan <- clientv3.WatchResponse{Canceled: true}
|
||||
close(responseChan)
|
||||
return responseChan
|
||||
}
|
||||
|
||||
w := newWatcher(c.cfg.PerWatcherBufferSize, func(k []byte) bool { return strings.HasPrefix(string(k), key) })
|
||||
c.demux.Register(w, startRev)
|
||||
|
||||
responseChan := make(chan clientv3.WatchResponse)
|
||||
c.waitGroup.Add(1)
|
||||
go func() {
|
||||
defer c.waitGroup.Done()
|
||||
defer close(responseChan)
|
||||
defer c.demux.Unregister(w)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-c.internalCtx.Done():
|
||||
return
|
||||
case events, ok := <-w.eventQueue:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-c.internalCtx.Done():
|
||||
return
|
||||
case responseChan <- clientv3.WatchResponse{Events: events}:
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return responseChan
|
||||
}
|
||||
|
||||
// Ready reports whether the cache has finished its initial load.
|
||||
func (c *Cache) Ready() bool {
|
||||
select {
|
||||
case <-c.ready:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// WaitReady blocks until the cache is ready or the ctx is cancelled.
|
||||
func (c *Cache) WaitReady(ctx context.Context) error {
|
||||
select {
|
||||
case <-c.ready:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// Close cancels the private context and blocks until all goroutines return.
|
||||
func (c *Cache) Close() {
|
||||
c.stop()
|
||||
c.waitGroup.Wait()
|
||||
}
|
||||
|
||||
func serveWatchEvents(ctx context.Context, watchCtx *watchCtx) {
|
||||
backoff := watchCtx.backoffStart
|
||||
for {
|
||||
opts := []clientv3.OpOption{
|
||||
clientv3.WithPrefix(),
|
||||
clientv3.WithProgressNotify(),
|
||||
clientv3.WithCreatedNotify(),
|
||||
}
|
||||
if oldestRev := watchCtx.cache.demux.PeekOldest(); oldestRev != 0 {
|
||||
opts = append(opts,
|
||||
clientv3.WithRev(oldestRev+1))
|
||||
}
|
||||
watchCh := watchCtx.cache.watcher.Watch(ctx, watchCtx.cache.prefix, opts...)
|
||||
|
||||
if err := readWatchChannel(watchCh, watchCtx.cache, watchCtx.cache.demux, watchCtx.onFirstResponse); err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.After(backoff):
|
||||
}
|
||||
if backoff < watchCtx.backoffMax {
|
||||
backoff *= 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// readWatchChannel reads an etcd Watch stream into History and enqueueCh, returning nil on cancel or first error.
|
||||
func readWatchChannel(
|
||||
watchChan clientv3.WatchChan,
|
||||
cache *Cache,
|
||||
demux *demux,
|
||||
onFirstResponse func(),
|
||||
) error {
|
||||
for resp := range watchChan {
|
||||
onFirstResponse()
|
||||
|
||||
if err := resp.Err(); err != nil {
|
||||
if errors.Is(err, rpctypes.ErrCompacted) {
|
||||
select {
|
||||
case <-cache.ready:
|
||||
// TODO: Reinitialize cache.ready safely; current direct channel assignment can race with concurrent watchers
|
||||
cache.ready = make(chan struct{})
|
||||
default:
|
||||
}
|
||||
demux.Purge()
|
||||
}
|
||||
return err
|
||||
}
|
||||
demux.Broadcast(resp.Events)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
// Copyright 2025 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
|
||||
import "time"
|
||||
|
||||
type Config struct {
|
||||
// PerWatcherBufferSize caps each watcher’s buffered channel.
|
||||
// Bigger values tolerate brief client slow-downs at the cost of extra memory.
|
||||
PerWatcherBufferSize int
|
||||
// HistoryWindowSize is the max events kept in memory for replay.
|
||||
// It defines how far back the cache can replay events to lagging watchers
|
||||
HistoryWindowSize int
|
||||
// ResyncInterval controls how often the demux attempts to catch a lagging watcher up by replaying events from History.
|
||||
ResyncInterval time.Duration
|
||||
// InitialBackoff is the first delay to wait before retrying an upstream etcd Watch after it ends with an error.
|
||||
InitialBackoff time.Duration
|
||||
// MaxBackoff caps the exponential back-off between successive upstream watch retries.
|
||||
MaxBackoff time.Duration
|
||||
}
|
||||
|
||||
// TODO: tune via performance/load tests.
|
||||
func defaultConfig() Config {
|
||||
return Config{
|
||||
PerWatcherBufferSize: 10,
|
||||
HistoryWindowSize: 2048,
|
||||
ResyncInterval: 50 * time.Millisecond,
|
||||
InitialBackoff: 50 * time.Millisecond,
|
||||
MaxBackoff: 2 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
type Option func(*Config)
|
||||
|
||||
func WithPerWatcherBufferSize(n int) Option {
|
||||
return func(c *Config) { c.PerWatcherBufferSize = n }
|
||||
}
|
||||
|
||||
func WithHistoryWindowSize(n int) Option {
|
||||
return func(c *Config) { c.HistoryWindowSize = n }
|
||||
}
|
||||
|
||||
func WithResyncInterval(d time.Duration) Option {
|
||||
return func(c *Config) { c.ResyncInterval = d }
|
||||
}
|
||||
|
||||
func WithInitialBackoff(d time.Duration) Option {
|
||||
return func(c *Config) { c.InitialBackoff = d }
|
||||
}
|
||||
|
||||
func WithMaxBackoff(d time.Duration) Option {
|
||||
return func(c *Config) { c.MaxBackoff = d }
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
// Copyright 2025 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
)
|
||||
|
||||
type demux struct {
|
||||
mu sync.RWMutex
|
||||
// activeWatchers & laggingWatchers hold the first revision the watcher still needs (nextRev).
|
||||
activeWatchers map[*watcher]int64
|
||||
laggingWatchers map[*watcher]int64
|
||||
history *ringBuffer
|
||||
resyncInterval time.Duration
|
||||
}
|
||||
|
||||
func NewDemux(ctx context.Context, wg *sync.WaitGroup, historyWindowSize int, resyncInterval time.Duration) *demux {
|
||||
d := newDemux(historyWindowSize, resyncInterval)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
d.resyncLoop(ctx)
|
||||
}()
|
||||
return d
|
||||
}
|
||||
|
||||
func newDemux(historyWindowSize int, resyncInterval time.Duration) *demux {
|
||||
return &demux{
|
||||
activeWatchers: make(map[*watcher]int64),
|
||||
laggingWatchers: make(map[*watcher]int64),
|
||||
history: newRingBuffer(historyWindowSize),
|
||||
resyncInterval: resyncInterval,
|
||||
}
|
||||
}
|
||||
|
||||
// resyncLoop periodically tries to catch lagging watchers up by replaying events from History.
|
||||
func (d *demux) resyncLoop(ctx context.Context) {
|
||||
timer := time.NewTimer(d.resyncInterval)
|
||||
defer timer.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-timer.C:
|
||||
d.resyncLaggingWatchers()
|
||||
timer.Reset(d.resyncInterval)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *demux) Register(w *watcher, startingRev int64) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
latestRev := d.history.PeekLatest()
|
||||
|
||||
// Special case: 0 means “newest”.
|
||||
if startingRev == 0 {
|
||||
if latestRev == 0 {
|
||||
d.activeWatchers[w] = 0
|
||||
return
|
||||
}
|
||||
startingRev = latestRev + 1
|
||||
}
|
||||
|
||||
if startingRev <= latestRev {
|
||||
d.laggingWatchers[w] = startingRev
|
||||
} else {
|
||||
d.activeWatchers[w] = startingRev
|
||||
}
|
||||
}
|
||||
|
||||
func (d *demux) Unregister(w *watcher) {
|
||||
func() {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
delete(d.activeWatchers, w)
|
||||
delete(d.laggingWatchers, w)
|
||||
}()
|
||||
w.Stop()
|
||||
}
|
||||
|
||||
func (d *demux) Broadcast(events []*clientv3.Event) {
|
||||
if len(events) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
d.history.Append(events)
|
||||
|
||||
lastRev := events[len(events)-1].Kv.ModRevision
|
||||
for w, nextRev := range d.activeWatchers {
|
||||
start := len(events)
|
||||
for i, ev := range events {
|
||||
if ev.Kv.ModRevision >= nextRev {
|
||||
start = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if start == len(events) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !w.enqueueEvent(events[start:]) { // overflow → lagging
|
||||
d.laggingWatchers[w] = nextRev
|
||||
delete(d.activeWatchers, w)
|
||||
} else {
|
||||
d.activeWatchers[w] = lastRev + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Purge is called when etcd compaction invalidates our cached history, so clients should resubscribe.
|
||||
func (d *demux) Purge() {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
d.history.RebaseHistory()
|
||||
for w := range d.activeWatchers {
|
||||
w.Stop()
|
||||
}
|
||||
for w := range d.laggingWatchers {
|
||||
w.Stop()
|
||||
}
|
||||
d.activeWatchers = make(map[*watcher]int64)
|
||||
d.laggingWatchers = make(map[*watcher]int64)
|
||||
}
|
||||
|
||||
func (d *demux) resyncLaggingWatchers() {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
oldestRev := d.history.PeekOldest()
|
||||
for w, nextRev := range d.laggingWatchers {
|
||||
if oldestRev != 0 && nextRev < oldestRev {
|
||||
w.Stop()
|
||||
delete(d.laggingWatchers, w)
|
||||
continue
|
||||
}
|
||||
// TODO: re-enable key‐predicate in Filter when non‐zero startRev or performance tuning is needed
|
||||
missed := d.history.Filter(nextRev)
|
||||
|
||||
enqueueFailed := false
|
||||
for _, eventBatch := range missed {
|
||||
if !w.enqueueEvent(eventBatch) { // buffer overflow: watcher still lagging
|
||||
enqueueFailed = true
|
||||
break
|
||||
}
|
||||
nextRev = eventBatch[0].Kv.ModRevision + 1
|
||||
}
|
||||
|
||||
if !enqueueFailed {
|
||||
delete(d.laggingWatchers, w)
|
||||
d.activeWatchers[w] = nextRev
|
||||
} else {
|
||||
d.laggingWatchers[w] = nextRev
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *demux) PeekOldest() int64 {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
return d.history.PeekOldest()
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
// Copyright 2025 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"go.etcd.io/etcd/api/v3/mvccpb"
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
)
|
||||
|
||||
func TestBroadcastBatching(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []int64
|
||||
wantRevs []int64
|
||||
wantSizes []int
|
||||
}{
|
||||
{
|
||||
name: "two groups",
|
||||
input: []int64{14, 14, 15, 15, 15},
|
||||
wantRevs: []int64{14},
|
||||
wantSizes: []int{5},
|
||||
},
|
||||
{
|
||||
name: "single group",
|
||||
input: []int64{7, 7, 7},
|
||||
wantRevs: []int64{7},
|
||||
wantSizes: []int{3},
|
||||
},
|
||||
{
|
||||
name: "all distinct",
|
||||
input: []int64{1, 2, 3},
|
||||
wantRevs: []int64{1},
|
||||
wantSizes: []int{3},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
d := newDemux(16, 10*time.Millisecond)
|
||||
w := newWatcher(len(tt.input)+1, nil)
|
||||
d.Register(w, 0)
|
||||
|
||||
d.Broadcast(eventRevs(tt.input...))
|
||||
|
||||
gotRevs, gotSizes := readBatches(t, w, len(tt.wantRevs))
|
||||
|
||||
if diff := cmp.Diff(tt.wantRevs, gotRevs); diff != "" {
|
||||
t.Fatalf("revision mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(tt.wantSizes, gotSizes); diff != "" {
|
||||
t.Fatalf("batch size mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSlowWatcherResync(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []int64
|
||||
wantInitialRevs []int64
|
||||
wantInitialSizes []int
|
||||
wantResyncRevs []int64
|
||||
wantResyncSizes []int
|
||||
}{
|
||||
{
|
||||
name: "single event overflow",
|
||||
input: []int64{1, 2, 3},
|
||||
wantInitialRevs: []int64{1},
|
||||
wantInitialSizes: []int{3},
|
||||
wantResyncRevs: []int64{},
|
||||
wantResyncSizes: []int{},
|
||||
},
|
||||
{
|
||||
name: "multi events batch overflow",
|
||||
input: []int64{10, 10, 11, 12, 12},
|
||||
wantInitialRevs: []int64{10},
|
||||
wantInitialSizes: []int{5},
|
||||
wantResyncRevs: []int64{},
|
||||
wantResyncSizes: []int{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
d := newDemux(16, 10*time.Millisecond)
|
||||
w := newWatcher(1, nil)
|
||||
d.Register(w, 0)
|
||||
|
||||
d.Broadcast(eventRevs(tt.input...))
|
||||
|
||||
gotInitRevs, gotInitSizes := readBatches(t, w, len(tt.wantInitialRevs))
|
||||
if diff := cmp.Diff(tt.wantInitialRevs, gotInitRevs); diff != "" {
|
||||
t.Fatalf("initial revs mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(tt.wantInitialSizes, gotInitSizes); diff != "" {
|
||||
t.Fatalf("initial batch sizes mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
|
||||
gotRevs, gotSizes := make([]int64, 0, len(tt.wantResyncRevs)), make([]int, 0, len(tt.wantResyncRevs))
|
||||
for len(gotRevs) < len(tt.wantResyncRevs) {
|
||||
d.resyncLaggingWatchers()
|
||||
revs, sizes := readBatches(t, w, 1)
|
||||
gotRevs = append(gotRevs, revs...)
|
||||
gotSizes = append(gotSizes, sizes...)
|
||||
}
|
||||
if diff := cmp.Diff(tt.wantResyncRevs, gotRevs); diff != "" {
|
||||
t.Fatalf("resync revs mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(tt.wantResyncSizes, gotSizes); diff != "" {
|
||||
t.Fatalf("resync batch sizes mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func eventRevs(revs ...int64) []*clientv3.Event {
|
||||
events := make([]*clientv3.Event, 0, len(revs))
|
||||
for _, r := range revs {
|
||||
kv := &mvccpb.KeyValue{
|
||||
Key: []byte("k"),
|
||||
Value: []byte("v"),
|
||||
ModRevision: r,
|
||||
}
|
||||
events = append(events, &clientv3.Event{
|
||||
Type: clientv3.EventTypePut,
|
||||
Kv: kv,
|
||||
})
|
||||
}
|
||||
return events
|
||||
}
|
||||
|
||||
func readBatches(t *testing.T, w *watcher, n int) (revs []int64, sizes []int) {
|
||||
t.Helper()
|
||||
timeout := time.After(2 * time.Second)
|
||||
for len(revs) < n {
|
||||
select {
|
||||
case batch := <-w.eventQueue:
|
||||
revs = append(revs, batch[0].Kv.ModRevision)
|
||||
sizes = append(sizes, len(batch))
|
||||
case <-timeout:
|
||||
t.Fatalf("timed out waiting for %d batches; got %d", n, len(revs))
|
||||
}
|
||||
}
|
||||
return revs, sizes
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
module go.etcd.io/etcd/cache/v3
|
||||
|
||||
go 1.24
|
||||
|
||||
toolchain go1.24.4
|
||||
|
||||
require (
|
||||
github.com/google/go-cmp v0.7.0
|
||||
go.etcd.io/etcd/api/v3 v3.6.0-alpha.0
|
||||
go.etcd.io/etcd/client/v3 v3.6.0-alpha.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/net v0.41.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
|
||||
google.golang.org/grpc v1.73.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
go.etcd.io/etcd/api/v3 => ../api
|
||||
go.etcd.io/etcd/client/pkg/v3 => ../client/pkg
|
||||
go.etcd.io/etcd/client/v3 => ../client/v3
|
||||
)
|
|
@ -0,0 +1,107 @@
|
|||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
||||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
|
||||
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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
||||
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
||||
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.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
||||
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
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/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2015 The etcd Authors
|
||||
// Copyright 2025 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -12,26 +12,14 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
package cache
|
||||
|
||||
import "fmt"
|
||||
type Prefix string
|
||||
|
||||
type ClusterError struct {
|
||||
Errors []error
|
||||
}
|
||||
|
||||
func (ce *ClusterError) Error() string {
|
||||
s := ErrClusterUnavailable.Error()
|
||||
for i, e := range ce.Errors {
|
||||
s += fmt.Sprintf("; error #%d: %s\n", i, e)
|
||||
func (prefix Prefix) Match(key []byte) bool {
|
||||
if prefix == "" {
|
||||
return true
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (ce *ClusterError) Detail() string {
|
||||
s := ""
|
||||
for i, e := range ce.Errors {
|
||||
s += fmt.Sprintf("error #%d: %s\n", i, e)
|
||||
}
|
||||
return s
|
||||
prefixLen := len(prefix)
|
||||
return len(key) >= prefixLen && string(key[:prefixLen]) == string(prefix)
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
// Copyright 2025 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
)
|
||||
|
||||
type ringBuffer struct {
|
||||
buffer []batch
|
||||
// head is the index immediately after the last non-empty entry in the buffer (i.e., the next write position).
|
||||
head, tail, size int
|
||||
}
|
||||
|
||||
// batch groups all events that share one ModRevision.
|
||||
type batch struct {
|
||||
rev int64
|
||||
events []*clientv3.Event
|
||||
}
|
||||
|
||||
type KeyPredicate = func([]byte) bool
|
||||
|
||||
func newRingBuffer(capacity int) *ringBuffer {
|
||||
// assume capacity > 0 – validated by Cache
|
||||
return &ringBuffer{
|
||||
buffer: make([]batch, capacity),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ringBuffer) Append(events []*clientv3.Event) {
|
||||
start := 0
|
||||
for end := 1; end < len(events); end++ {
|
||||
if events[end].Kv.ModRevision != events[start].Kv.ModRevision {
|
||||
r.append(batch{
|
||||
rev: events[start].Kv.ModRevision,
|
||||
events: events[start:end],
|
||||
})
|
||||
start = end
|
||||
}
|
||||
}
|
||||
if start < len(events) {
|
||||
r.append(batch{
|
||||
rev: events[start].Kv.ModRevision,
|
||||
events: events[start:],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ringBuffer) append(b batch) {
|
||||
if len(b.events) == 0 {
|
||||
return
|
||||
}
|
||||
if r.size == len(r.buffer) {
|
||||
r.tail = (r.tail + 1) % len(r.buffer)
|
||||
} else {
|
||||
r.size++
|
||||
}
|
||||
r.buffer[r.head] = b
|
||||
r.head = (r.head + 1) % len(r.buffer)
|
||||
}
|
||||
|
||||
// Filter returns all events in the buffer whose ModRevision is >= minRev.
|
||||
// TODO: use binary search on the ring buffer to locate the first entry >= nextRev instead of a full scan
|
||||
func (r *ringBuffer) Filter(minRev int64) (eventBatches [][]*clientv3.Event) {
|
||||
eventBatches = make([][]*clientv3.Event, 0, r.size)
|
||||
|
||||
for n, i := 0, r.tail; n < r.size; n, i = n+1, (i+1)%len(r.buffer) {
|
||||
eventBatch := r.buffer[i]
|
||||
if eventBatch.events == nil {
|
||||
panic(fmt.Sprintf("ringBuffer.Filter: unexpected nil eventBatch at index %d", i))
|
||||
}
|
||||
if eventBatch.rev >= minRev {
|
||||
eventBatches = append(eventBatches, eventBatch.events)
|
||||
}
|
||||
}
|
||||
return eventBatches
|
||||
}
|
||||
|
||||
// PeekLatest returns the most recently-appended event (or nil if empty).
|
||||
func (r *ringBuffer) PeekLatest() int64 {
|
||||
if r.size == 0 {
|
||||
return 0
|
||||
}
|
||||
idx := (r.head - 1 + len(r.buffer)) % len(r.buffer)
|
||||
return r.buffer[idx].rev
|
||||
}
|
||||
|
||||
// PeekOldest returns the oldest event currently stored (or nil if empty).
|
||||
func (r *ringBuffer) PeekOldest() int64 {
|
||||
if r.size == 0 {
|
||||
return 0
|
||||
}
|
||||
return r.buffer[r.tail].rev
|
||||
}
|
||||
|
||||
func (r *ringBuffer) RebaseHistory() {
|
||||
r.head, r.tail, r.size = 0, 0, 0
|
||||
for i := range r.buffer {
|
||||
r.buffer[i] = batch{}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,308 @@
|
|||
// Copyright 2025 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"go.etcd.io/etcd/api/v3/mvccpb"
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
)
|
||||
|
||||
func TestPeekLatestAndOldest(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
capacity int
|
||||
revs []int64
|
||||
wantLatestRev int64
|
||||
wantOldestRev int64
|
||||
}{
|
||||
{
|
||||
name: "empty_buffer",
|
||||
capacity: 4,
|
||||
revs: nil,
|
||||
wantLatestRev: 0,
|
||||
wantOldestRev: 0,
|
||||
},
|
||||
{
|
||||
name: "single_element",
|
||||
capacity: 8,
|
||||
revs: []int64{1},
|
||||
wantLatestRev: 1,
|
||||
wantOldestRev: 1,
|
||||
},
|
||||
{
|
||||
name: "ascending_fill",
|
||||
capacity: 4,
|
||||
revs: []int64{1, 2, 3, 4},
|
||||
wantLatestRev: 4,
|
||||
wantOldestRev: 1,
|
||||
},
|
||||
{
|
||||
name: "overwrite_when_full",
|
||||
capacity: 3,
|
||||
revs: []int64{5, 6, 7, 8},
|
||||
wantLatestRev: 8,
|
||||
wantOldestRev: 6,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
rb := newRingBuffer(tt.capacity)
|
||||
for _, r := range tt.revs {
|
||||
batch, err := makeEventBatch(r, "k", 1)
|
||||
if err != nil {
|
||||
t.Fatalf("makeEventBatch(%d, k, 1) failed: %v", r, err)
|
||||
}
|
||||
rb.Append(batch)
|
||||
}
|
||||
|
||||
latestRev := rb.PeekLatest()
|
||||
oldestRev := rb.PeekOldest()
|
||||
|
||||
gotLatestRev := latestRev
|
||||
gotOldestRev := oldestRev
|
||||
|
||||
if tt.wantLatestRev != gotLatestRev {
|
||||
t.Fatalf("PeekLatest()=%d, want=%d", gotLatestRev, tt.wantLatestRev)
|
||||
}
|
||||
if tt.wantOldestRev != gotOldestRev {
|
||||
t.Fatalf("PeekOldest()=%d, want=%d", gotOldestRev, tt.wantOldestRev)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
capacity int
|
||||
revs []int64
|
||||
minRev int64
|
||||
wantFilteredRevs []int64
|
||||
wantLatestRev int64
|
||||
}{
|
||||
{
|
||||
name: "no_filter",
|
||||
capacity: 5,
|
||||
revs: []int64{1, 2, 3},
|
||||
minRev: 0,
|
||||
wantFilteredRevs: []int64{1, 2, 3},
|
||||
wantLatestRev: 3,
|
||||
},
|
||||
{
|
||||
name: "partial_match",
|
||||
capacity: 5,
|
||||
revs: []int64{10, 11, 12, 13},
|
||||
minRev: 12,
|
||||
wantFilteredRevs: []int64{12, 13},
|
||||
wantLatestRev: 13,
|
||||
},
|
||||
{
|
||||
name: "filter_when_full",
|
||||
capacity: 3,
|
||||
revs: []int64{20, 21, 22, 23, 24},
|
||||
minRev: 23,
|
||||
wantFilteredRevs: []int64{23, 24},
|
||||
wantLatestRev: 24,
|
||||
},
|
||||
{
|
||||
name: "none_match",
|
||||
capacity: 4,
|
||||
revs: []int64{30, 31},
|
||||
minRev: 100,
|
||||
wantFilteredRevs: []int64{},
|
||||
wantLatestRev: 31,
|
||||
},
|
||||
{
|
||||
name: "empty_buffer",
|
||||
capacity: 3,
|
||||
revs: nil,
|
||||
minRev: 0,
|
||||
wantFilteredRevs: []int64{},
|
||||
wantLatestRev: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
rb := newRingBuffer(tt.capacity)
|
||||
for _, r := range tt.revs {
|
||||
batch, err := makeEventBatch(r, "k", 11)
|
||||
if err != nil {
|
||||
t.Fatalf("makeEventBatch(%d, k, 11) failed: %v", r, err)
|
||||
}
|
||||
rb.Append(batch)
|
||||
}
|
||||
|
||||
gotBatches := rb.Filter(tt.minRev)
|
||||
gotRevs := make([]int64, len(gotBatches))
|
||||
for i, b := range gotBatches {
|
||||
gotRevs[i] = b[0].Kv.ModRevision
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tt.wantFilteredRevs, gotRevs); diff != "" {
|
||||
t.Fatalf("Filter() revisions mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAtomicOrdered(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
capacity int
|
||||
inputs []struct {
|
||||
rev int64
|
||||
key string
|
||||
size int
|
||||
}
|
||||
wantRev []int64
|
||||
wantSize []int
|
||||
}{
|
||||
{
|
||||
name: "unfiltered",
|
||||
capacity: 5,
|
||||
inputs: []struct {
|
||||
rev int64
|
||||
key string
|
||||
size int
|
||||
}{
|
||||
{5, "a", 1},
|
||||
{10, "b", 3},
|
||||
{15, "c", 7},
|
||||
{20, "d", 11},
|
||||
},
|
||||
wantRev: []int64{5, 10, 15, 20},
|
||||
wantSize: []int{1, 3, 7, 11},
|
||||
},
|
||||
{
|
||||
name: "across_wrap",
|
||||
capacity: 3,
|
||||
inputs: []struct {
|
||||
rev int64
|
||||
key string
|
||||
size int
|
||||
}{
|
||||
{1, "a", 2},
|
||||
{2, "b", 1},
|
||||
{3, "c", 3},
|
||||
{4, "d", 7},
|
||||
},
|
||||
wantRev: []int64{2, 3, 4},
|
||||
wantSize: []int{1, 3, 7},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rb := newRingBuffer(tt.capacity)
|
||||
for _, in := range tt.inputs {
|
||||
batch, err := makeEventBatch(in.rev, in.key, in.size)
|
||||
if err != nil {
|
||||
t.Fatalf("makeEventBatch(%d, k, 1) failed: %v", in.rev, err)
|
||||
}
|
||||
rb.Append(batch)
|
||||
}
|
||||
|
||||
gotBatches := rb.Filter(0)
|
||||
if len(gotBatches) != len(tt.wantRev) {
|
||||
t.Fatalf("len(got) = %d, want %d", len(gotBatches), len(tt.wantRev))
|
||||
}
|
||||
for i, b := range gotBatches {
|
||||
if b[0].Kv.ModRevision != tt.wantRev[i] {
|
||||
t.Errorf("at idx %d: rev = %d, want %d", i, b[0].Kv.ModRevision, tt.wantRev[i])
|
||||
}
|
||||
if batchSize := len(b); batchSize != tt.wantSize[i] {
|
||||
t.Errorf("at rev %d: events.len = %d, want %d", b[0].Kv.ModRevision, batchSize, tt.wantSize[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRebaseHistory(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
revs []int64
|
||||
}{
|
||||
{
|
||||
name: "rebase_empty_buffer",
|
||||
revs: nil,
|
||||
},
|
||||
{
|
||||
name: "rebase_after_data",
|
||||
revs: []int64{7, 8, 9},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
rb := newRingBuffer(4)
|
||||
for _, r := range tt.revs {
|
||||
batch, err := makeEventBatch(r, "k", 1)
|
||||
if err != nil {
|
||||
t.Fatalf("makeEventBatch(%d, k, 1) failed: %v", r, err)
|
||||
}
|
||||
rb.Append(batch)
|
||||
}
|
||||
|
||||
rb.RebaseHistory()
|
||||
|
||||
oldestRev := rb.PeekOldest()
|
||||
|
||||
latestRev := rb.PeekLatest()
|
||||
|
||||
if oldestRev != 0 {
|
||||
t.Fatalf("PeekOldest()=%d, want=%d", oldestRev, 0)
|
||||
}
|
||||
if latestRev != 0 {
|
||||
t.Fatalf("PeekLatest()=%d, want=%d", latestRev, 0)
|
||||
}
|
||||
|
||||
batches := rb.Filter(0)
|
||||
if len(batches) != 0 {
|
||||
t.Fatalf("Filter() len(events)=%d, want=%d", len(batches), 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func makeEventBatch(rev int64, key string, batchSize int) ([]*clientv3.Event, error) {
|
||||
if batchSize < 0 {
|
||||
return nil, fmt.Errorf("invalid batchSize %d", batchSize)
|
||||
}
|
||||
events := make([]*clientv3.Event, batchSize)
|
||||
for i := range events {
|
||||
events[i] = &clientv3.Event{
|
||||
Kv: &mvccpb.KeyValue{
|
||||
Key: []byte(fmt.Sprintf("%s-%d", key, i)),
|
||||
ModRevision: rev,
|
||||
},
|
||||
}
|
||||
}
|
||||
return events, nil
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
// Copyright 2025 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
)
|
||||
|
||||
// watcher holds one client’s buffered stream of events.
|
||||
type watcher struct {
|
||||
eventQueue chan []*clientv3.Event
|
||||
keyPred KeyPredicate
|
||||
stopped int32
|
||||
done chan struct{} // closed together with Stop()
|
||||
}
|
||||
|
||||
func newWatcher(bufSize int, pred KeyPredicate) *watcher {
|
||||
return &watcher{
|
||||
eventQueue: make(chan []*clientv3.Event, bufSize),
|
||||
keyPred: pred,
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// true -> events delivered (or filtered/duplicate)
|
||||
// false -> buffer full (caller should mark watcher “lagging”)
|
||||
func (w *watcher) enqueueEvent(eventBatch []*clientv3.Event) bool {
|
||||
if w.keyPred != nil {
|
||||
filtered := make([]*clientv3.Event, 0, len(eventBatch))
|
||||
for _, event := range eventBatch {
|
||||
if w.keyPred(event.Kv.Key) {
|
||||
filtered = append(filtered, event)
|
||||
}
|
||||
}
|
||||
if len(filtered) == 0 {
|
||||
return true
|
||||
}
|
||||
eventBatch = filtered
|
||||
}
|
||||
select {
|
||||
case w.eventQueue <- eventBatch:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Stop closes the event channel atomically.
|
||||
func (w *watcher) Stop() {
|
||||
if atomic.CompareAndSwapInt32(&w.stopped, 0, 1) {
|
||||
close(w.eventQueue)
|
||||
close(w.done)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *watcher) Done() <-chan struct{} { return w.done }
|
|
@ -1,113 +0,0 @@
|
|||
# etcd/client
|
||||
|
||||
etcd/client is the Go client library for etcd.
|
||||
|
||||
[](https://godoc.org/go.etcd.io/etcd/client)
|
||||
|
||||
For full compatibility, it is recommended to install released versions of clients using go modules.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get go.etcd.io/etcd/v3/client
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"go.etcd.io/etcd/v3/client"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg := client.Config{
|
||||
Endpoints: []string{"http://127.0.0.1:2379"},
|
||||
Transport: client.DefaultTransport,
|
||||
// set timeout per request to fail fast when the target endpoint is unavailable
|
||||
HeaderTimeoutPerRequest: time.Second,
|
||||
}
|
||||
c, err := client.New(cfg)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
kapi := client.NewKeysAPI(c)
|
||||
// set "/foo" key with "bar" value
|
||||
log.Print("Setting '/foo' key with 'bar' value")
|
||||
resp, err := kapi.Set(context.Background(), "/foo", "bar", nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
} else {
|
||||
// print common key info
|
||||
log.Printf("Set is done. Metadata is %q\n", resp)
|
||||
}
|
||||
// get "/foo" key's value
|
||||
log.Print("Getting '/foo' key value")
|
||||
resp, err = kapi.Get(context.Background(), "/foo", nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
} else {
|
||||
// print common key info
|
||||
log.Printf("Get is done. Metadata is %q\n", resp)
|
||||
// print value
|
||||
log.Printf("%q key has %q value\n", resp.Node.Key, resp.Node.Value)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
etcd client might return three types of errors.
|
||||
|
||||
- context error
|
||||
|
||||
Each API call has its first parameter as `context`. A context can be canceled or have an attached deadline. If the context is canceled or reaches its deadline, the responding context error will be returned no matter what internal errors the API call has already encountered.
|
||||
|
||||
- cluster error
|
||||
|
||||
Each API call tries to send request to the cluster endpoints one by one until it successfully gets a response. If a requests to an endpoint fails, due to exceeding per request timeout or connection issues, the error will be added into a list of errors. If all possible endpoints fail, a cluster error that includes all encountered errors will be returned.
|
||||
|
||||
- response error
|
||||
|
||||
If the response gets from the cluster is invalid, a plain string error will be returned. For example, it might be a invalid JSON error.
|
||||
|
||||
Here is the example code to handle client errors:
|
||||
|
||||
```go
|
||||
cfg := client.Config{Endpoints: []string{"http://etcd1:2379","http://etcd2:2379","http://etcd3:2379"}}
|
||||
c, err := client.New(cfg)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
kapi := client.NewKeysAPI(c)
|
||||
resp, err := kapi.Set(ctx, "test", "bar", nil)
|
||||
if err != nil {
|
||||
var cerr *client.ClusterError
|
||||
if errors.Is(err, context.Canceled) {
|
||||
// ctx is canceled by another routine
|
||||
} else if errors.Is(err, context.DeadlineExceeded) {
|
||||
// ctx is attached with a deadline and it exceeded
|
||||
} else if errors.As(err, &cerr) {
|
||||
// process (cerr.Errors)
|
||||
} else {
|
||||
// bad cluster endpoints, which are not etcd servers
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Caveat
|
||||
|
||||
1. etcd/client prefers to use the same endpoint as long as the endpoint continues to work well. This saves socket resources, and improves efficiency for both client and server side. This preference doesn't remove consistency from the data consumed by the client because data replicated to each etcd member has already passed through the consensus process.
|
||||
|
||||
2. etcd/client does round-robin rotation on other available endpoints if the preferred endpoint isn't functioning properly. For example, if the member that etcd/client connects to is hard killed, etcd/client will fail on the first attempt with the killed member, and succeed on the second attempt with another member. If it fails to talk to all available endpoints, it will return all errors happened.
|
||||
|
||||
3. Default etcd/client cannot handle the case that the remote server is SIGSTOPed now. TCP keepalive mechanism doesn't help in this scenario because operating system may still send TCP keep-alive packets. Over time we'd like to improve this functionality, but solving this issue isn't high priority because a real-life case in which a server is stopped, but the connection is kept alive, hasn't been brought to our attention.
|
||||
|
||||
4. etcd/client cannot detect whether a member is healthy with watches and non-quorum read requests. If the member is isolated from the cluster, etcd/client may retrieve outdated data. Instead, users can either issue quorum read requests or monitor the /health endpoint for member health information.
|
|
@ -1,236 +0,0 @@
|
|||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type Role struct {
|
||||
Role string `json:"role"`
|
||||
Permissions Permissions `json:"permissions"`
|
||||
Grant *Permissions `json:"grant,omitempty"`
|
||||
Revoke *Permissions `json:"revoke,omitempty"`
|
||||
}
|
||||
|
||||
type Permissions struct {
|
||||
KV rwPermission `json:"kv"`
|
||||
}
|
||||
|
||||
type rwPermission struct {
|
||||
Read []string `json:"read"`
|
||||
Write []string `json:"write"`
|
||||
}
|
||||
|
||||
type PermissionType int
|
||||
|
||||
const (
|
||||
ReadPermission PermissionType = iota
|
||||
WritePermission
|
||||
ReadWritePermission
|
||||
)
|
||||
|
||||
// NewAuthRoleAPI constructs a new AuthRoleAPI that uses HTTP to
|
||||
// interact with etcd's role creation and modification features.
|
||||
func NewAuthRoleAPI(c Client) AuthRoleAPI {
|
||||
return &httpAuthRoleAPI{
|
||||
client: c,
|
||||
}
|
||||
}
|
||||
|
||||
type AuthRoleAPI interface {
|
||||
// AddRole adds a role.
|
||||
AddRole(ctx context.Context, role string) error
|
||||
|
||||
// RemoveRole removes a role.
|
||||
RemoveRole(ctx context.Context, role string) error
|
||||
|
||||
// GetRole retrieves role details.
|
||||
GetRole(ctx context.Context, role string) (*Role, error)
|
||||
|
||||
// GrantRoleKV grants a role some permission prefixes for the KV store.
|
||||
GrantRoleKV(ctx context.Context, role string, prefixes []string, permType PermissionType) (*Role, error)
|
||||
|
||||
// RevokeRoleKV revokes some permission prefixes for a role on the KV store.
|
||||
RevokeRoleKV(ctx context.Context, role string, prefixes []string, permType PermissionType) (*Role, error)
|
||||
|
||||
// ListRoles lists roles.
|
||||
ListRoles(ctx context.Context) ([]string, error)
|
||||
}
|
||||
|
||||
type httpAuthRoleAPI struct {
|
||||
client httpClient
|
||||
}
|
||||
|
||||
type authRoleAPIAction struct {
|
||||
verb string
|
||||
name string
|
||||
role *Role
|
||||
}
|
||||
|
||||
type authRoleAPIList struct{}
|
||||
|
||||
func (list *authRoleAPIList) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2AuthURL(ep, "roles", "")
|
||||
req, _ := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
return req
|
||||
}
|
||||
|
||||
func (l *authRoleAPIAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2AuthURL(ep, "roles", l.name)
|
||||
if l.role == nil {
|
||||
req, _ := http.NewRequest(l.verb, u.String(), nil)
|
||||
return req
|
||||
}
|
||||
b, err := json.Marshal(l.role)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
body := bytes.NewReader(b)
|
||||
req, _ := http.NewRequest(l.verb, u.String(), body)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
return req
|
||||
}
|
||||
|
||||
func (r *httpAuthRoleAPI) ListRoles(ctx context.Context) ([]string, error) {
|
||||
resp, body, err := r.client.Do(ctx, &authRoleAPIList{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var roleList struct {
|
||||
Roles []Role `json:"roles"`
|
||||
}
|
||||
if err = json.Unmarshal(body, &roleList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := make([]string, 0, len(roleList.Roles))
|
||||
for _, r := range roleList.Roles {
|
||||
ret = append(ret, r.Role)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *httpAuthRoleAPI) AddRole(ctx context.Context, rolename string) error {
|
||||
role := &Role{
|
||||
Role: rolename,
|
||||
}
|
||||
return r.addRemoveRole(ctx, &authRoleAPIAction{
|
||||
verb: http.MethodPut,
|
||||
name: rolename,
|
||||
role: role,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *httpAuthRoleAPI) RemoveRole(ctx context.Context, rolename string) error {
|
||||
return r.addRemoveRole(ctx, &authRoleAPIAction{
|
||||
verb: http.MethodDelete,
|
||||
name: rolename,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *httpAuthRoleAPI) addRemoveRole(ctx context.Context, req *authRoleAPIAction) error {
|
||||
resp, body, err := r.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := assertStatusCode(resp.StatusCode, http.StatusOK, http.StatusCreated); err != nil {
|
||||
var sec authError
|
||||
err := json.Unmarshal(body, &sec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sec
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *httpAuthRoleAPI) GetRole(ctx context.Context, rolename string) (*Role, error) {
|
||||
return r.modRole(ctx, &authRoleAPIAction{
|
||||
verb: http.MethodGet,
|
||||
name: rolename,
|
||||
})
|
||||
}
|
||||
|
||||
func buildRWPermission(prefixes []string, permType PermissionType) rwPermission {
|
||||
var out rwPermission
|
||||
switch permType {
|
||||
case ReadPermission:
|
||||
out.Read = prefixes
|
||||
case WritePermission:
|
||||
out.Write = prefixes
|
||||
case ReadWritePermission:
|
||||
out.Read = prefixes
|
||||
out.Write = prefixes
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (r *httpAuthRoleAPI) GrantRoleKV(ctx context.Context, rolename string, prefixes []string, permType PermissionType) (*Role, error) {
|
||||
rwp := buildRWPermission(prefixes, permType)
|
||||
role := &Role{
|
||||
Role: rolename,
|
||||
Grant: &Permissions{
|
||||
KV: rwp,
|
||||
},
|
||||
}
|
||||
return r.modRole(ctx, &authRoleAPIAction{
|
||||
verb: http.MethodPut,
|
||||
name: rolename,
|
||||
role: role,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *httpAuthRoleAPI) RevokeRoleKV(ctx context.Context, rolename string, prefixes []string, permType PermissionType) (*Role, error) {
|
||||
rwp := buildRWPermission(prefixes, permType)
|
||||
role := &Role{
|
||||
Role: rolename,
|
||||
Revoke: &Permissions{
|
||||
KV: rwp,
|
||||
},
|
||||
}
|
||||
return r.modRole(ctx, &authRoleAPIAction{
|
||||
verb: http.MethodPut,
|
||||
name: rolename,
|
||||
role: role,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *httpAuthRoleAPI) modRole(ctx context.Context, req *authRoleAPIAction) (*Role, error) {
|
||||
resp, body, err := r.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
var sec authError
|
||||
err = json.Unmarshal(body, &sec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, sec
|
||||
}
|
||||
var role Role
|
||||
if err = json.Unmarshal(body, &role); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &role, nil
|
||||
}
|
|
@ -1,317 +0,0 @@
|
|||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
)
|
||||
|
||||
var defaultV2AuthPrefix = "/v2/auth"
|
||||
|
||||
type User struct {
|
||||
User string `json:"user"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Roles []string `json:"roles"`
|
||||
Grant []string `json:"grant,omitempty"`
|
||||
Revoke []string `json:"revoke,omitempty"`
|
||||
}
|
||||
|
||||
// userListEntry is the user representation given by the server for ListUsers
|
||||
type userListEntry struct {
|
||||
User string `json:"user"`
|
||||
Roles []Role `json:"roles"`
|
||||
}
|
||||
|
||||
type UserRoles struct {
|
||||
User string `json:"user"`
|
||||
Roles []Role `json:"roles"`
|
||||
}
|
||||
|
||||
func v2AuthURL(ep url.URL, action string, name string) *url.URL {
|
||||
if name != "" {
|
||||
ep.Path = path.Join(ep.Path, defaultV2AuthPrefix, action, name)
|
||||
return &ep
|
||||
}
|
||||
ep.Path = path.Join(ep.Path, defaultV2AuthPrefix, action)
|
||||
return &ep
|
||||
}
|
||||
|
||||
// NewAuthAPI constructs a new AuthAPI that uses HTTP to
|
||||
// interact with etcd's general auth features.
|
||||
func NewAuthAPI(c Client) AuthAPI {
|
||||
return &httpAuthAPI{
|
||||
client: c,
|
||||
}
|
||||
}
|
||||
|
||||
type AuthAPI interface {
|
||||
// Enable auth.
|
||||
Enable(ctx context.Context) error
|
||||
|
||||
// Disable auth.
|
||||
Disable(ctx context.Context) error
|
||||
}
|
||||
|
||||
type httpAuthAPI struct {
|
||||
client httpClient
|
||||
}
|
||||
|
||||
func (s *httpAuthAPI) Enable(ctx context.Context) error {
|
||||
return s.enableDisable(ctx, &authAPIAction{http.MethodPut})
|
||||
}
|
||||
|
||||
func (s *httpAuthAPI) Disable(ctx context.Context) error {
|
||||
return s.enableDisable(ctx, &authAPIAction{http.MethodDelete})
|
||||
}
|
||||
|
||||
func (s *httpAuthAPI) enableDisable(ctx context.Context, req httpAction) error {
|
||||
resp, body, err := s.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK, http.StatusCreated); err != nil {
|
||||
var sec authError
|
||||
err = json.Unmarshal(body, &sec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sec
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type authAPIAction struct {
|
||||
verb string
|
||||
}
|
||||
|
||||
func (l *authAPIAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2AuthURL(ep, "enable", "")
|
||||
req, _ := http.NewRequest(l.verb, u.String(), nil)
|
||||
return req
|
||||
}
|
||||
|
||||
type authError struct {
|
||||
Message string `json:"message"`
|
||||
Code int `json:"-"`
|
||||
}
|
||||
|
||||
func (e authError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// NewAuthUserAPI constructs a new AuthUserAPI that uses HTTP to
|
||||
// interact with etcd's user creation and modification features.
|
||||
func NewAuthUserAPI(c Client) AuthUserAPI {
|
||||
return &httpAuthUserAPI{
|
||||
client: c,
|
||||
}
|
||||
}
|
||||
|
||||
type AuthUserAPI interface {
|
||||
// AddUser adds a user.
|
||||
AddUser(ctx context.Context, username string, password string) error
|
||||
|
||||
// RemoveUser removes a user.
|
||||
RemoveUser(ctx context.Context, username string) error
|
||||
|
||||
// GetUser retrieves user details.
|
||||
GetUser(ctx context.Context, username string) (*User, error)
|
||||
|
||||
// GrantUser grants a user some permission roles.
|
||||
GrantUser(ctx context.Context, username string, roles []string) (*User, error)
|
||||
|
||||
// RevokeUser revokes some permission roles from a user.
|
||||
RevokeUser(ctx context.Context, username string, roles []string) (*User, error)
|
||||
|
||||
// ChangePassword changes the user's password.
|
||||
ChangePassword(ctx context.Context, username string, password string) (*User, error)
|
||||
|
||||
// ListUsers lists the users.
|
||||
ListUsers(ctx context.Context) ([]string, error)
|
||||
}
|
||||
|
||||
type httpAuthUserAPI struct {
|
||||
client httpClient
|
||||
}
|
||||
|
||||
type authUserAPIAction struct {
|
||||
verb string
|
||||
username string
|
||||
user *User
|
||||
}
|
||||
|
||||
type authUserAPIList struct{}
|
||||
|
||||
func (list *authUserAPIList) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2AuthURL(ep, "users", "")
|
||||
req, _ := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
return req
|
||||
}
|
||||
|
||||
func (l *authUserAPIAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2AuthURL(ep, "users", l.username)
|
||||
if l.user == nil {
|
||||
req, _ := http.NewRequest(l.verb, u.String(), nil)
|
||||
return req
|
||||
}
|
||||
b, err := json.Marshal(l.user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
body := bytes.NewReader(b)
|
||||
req, _ := http.NewRequest(l.verb, u.String(), body)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
return req
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) ListUsers(ctx context.Context) ([]string, error) {
|
||||
resp, body, err := u.client.Do(ctx, &authUserAPIList{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
var sec authError
|
||||
err = json.Unmarshal(body, &sec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, sec
|
||||
}
|
||||
|
||||
var userList struct {
|
||||
Users []userListEntry `json:"users"`
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(body, &userList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := make([]string, 0, len(userList.Users))
|
||||
for _, u := range userList.Users {
|
||||
ret = append(ret, u.User)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) AddUser(ctx context.Context, username string, password string) error {
|
||||
user := &User{
|
||||
User: username,
|
||||
Password: password,
|
||||
}
|
||||
return u.addRemoveUser(ctx, &authUserAPIAction{
|
||||
verb: http.MethodPut,
|
||||
username: username,
|
||||
user: user,
|
||||
})
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) RemoveUser(ctx context.Context, username string) error {
|
||||
return u.addRemoveUser(ctx, &authUserAPIAction{
|
||||
verb: http.MethodDelete,
|
||||
username: username,
|
||||
})
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) addRemoveUser(ctx context.Context, req *authUserAPIAction) error {
|
||||
resp, body, err := u.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK, http.StatusCreated); err != nil {
|
||||
var sec authError
|
||||
err = json.Unmarshal(body, &sec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sec
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) GetUser(ctx context.Context, username string) (*User, error) {
|
||||
return u.modUser(ctx, &authUserAPIAction{
|
||||
verb: http.MethodGet,
|
||||
username: username,
|
||||
})
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) GrantUser(ctx context.Context, username string, roles []string) (*User, error) {
|
||||
user := &User{
|
||||
User: username,
|
||||
Grant: roles,
|
||||
}
|
||||
return u.modUser(ctx, &authUserAPIAction{
|
||||
verb: http.MethodPut,
|
||||
username: username,
|
||||
user: user,
|
||||
})
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) RevokeUser(ctx context.Context, username string, roles []string) (*User, error) {
|
||||
user := &User{
|
||||
User: username,
|
||||
Revoke: roles,
|
||||
}
|
||||
return u.modUser(ctx, &authUserAPIAction{
|
||||
verb: http.MethodPut,
|
||||
username: username,
|
||||
user: user,
|
||||
})
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) ChangePassword(ctx context.Context, username string, password string) (*User, error) {
|
||||
user := &User{
|
||||
User: username,
|
||||
Password: password,
|
||||
}
|
||||
return u.modUser(ctx, &authUserAPIAction{
|
||||
verb: http.MethodPut,
|
||||
username: username,
|
||||
user: user,
|
||||
})
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) modUser(ctx context.Context, req *authUserAPIAction) (*User, error) {
|
||||
resp, body, err := u.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
var sec authError
|
||||
err = json.Unmarshal(body, &sec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, sec
|
||||
}
|
||||
var user User
|
||||
if err = json.Unmarshal(body, &user); err != nil {
|
||||
var userR UserRoles
|
||||
if urerr := json.Unmarshal(body, &userR); urerr != nil {
|
||||
return nil, err
|
||||
}
|
||||
user.User = userR.User
|
||||
for _, r := range userR.Roles {
|
||||
user.Roles = append(user.Roles, r.Role)
|
||||
}
|
||||
}
|
||||
return &user, nil
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// borrowed from golang/net/context/ctxhttp/cancelreq.go
|
||||
|
||||
package client
|
||||
|
||||
import "net/http"
|
||||
|
||||
func requestCanceler(req *http.Request) func() {
|
||||
ch := make(chan struct{})
|
||||
req.Cancel = ch
|
||||
|
||||
return func() {
|
||||
close(ch)
|
||||
}
|
||||
}
|
|
@ -1,719 +0,0 @@
|
|||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.etcd.io/etcd/api/v3/version"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoEndpoints = errors.New("client: no endpoints available")
|
||||
ErrTooManyRedirects = errors.New("client: too many redirects")
|
||||
ErrClusterUnavailable = errors.New("client: etcd cluster is unavailable or misconfigured")
|
||||
ErrNoLeaderEndpoint = errors.New("client: no leader endpoint available")
|
||||
errTooManyRedirectChecks = errors.New("client: too many redirect checks")
|
||||
|
||||
// oneShotCtxValue is set on a context using WithValue(&oneShotValue) so
|
||||
// that Do() will not retry a request
|
||||
oneShotCtxValue any
|
||||
)
|
||||
|
||||
var DefaultRequestTimeout = 5 * time.Second
|
||||
|
||||
var DefaultTransport CancelableTransport = &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
type EndpointSelectionMode int
|
||||
|
||||
const (
|
||||
// EndpointSelectionRandom is the default value of the 'SelectionMode'.
|
||||
// As the name implies, the client object will pick a node from the members
|
||||
// of the cluster in a random fashion. If the cluster has three members, A, B,
|
||||
// and C, the client picks any node from its three members as its request
|
||||
// destination.
|
||||
EndpointSelectionRandom EndpointSelectionMode = iota
|
||||
|
||||
// If 'SelectionMode' is set to 'EndpointSelectionPrioritizeLeader',
|
||||
// requests are sent directly to the cluster leader. This reduces
|
||||
// forwarding roundtrips compared to making requests to etcd followers
|
||||
// who then forward them to the cluster leader. In the event of a leader
|
||||
// failure, however, clients configured this way cannot prioritize among
|
||||
// the remaining etcd followers. Therefore, when a client sets 'SelectionMode'
|
||||
// to 'EndpointSelectionPrioritizeLeader', it must use 'client.AutoSync()' to
|
||||
// maintain its knowledge of current cluster state.
|
||||
//
|
||||
// This mode should be used with Client.AutoSync().
|
||||
EndpointSelectionPrioritizeLeader
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
// Endpoints defines a set of URLs (schemes, hosts and ports only)
|
||||
// that can be used to communicate with a logical etcd cluster. For
|
||||
// example, a three-node cluster could be provided like so:
|
||||
//
|
||||
// Endpoints: []string{
|
||||
// "http://node1.example.com:2379",
|
||||
// "http://node2.example.com:2379",
|
||||
// "http://node3.example.com:2379",
|
||||
// }
|
||||
//
|
||||
// If multiple endpoints are provided, the Client will attempt to
|
||||
// use them all in the event that one or more of them are unusable.
|
||||
//
|
||||
// If Client.Sync is ever called, the Client may cache an alternate
|
||||
// set of endpoints to continue operation.
|
||||
Endpoints []string
|
||||
|
||||
// Transport is used by the Client to drive HTTP requests. If not
|
||||
// provided, DefaultTransport will be used.
|
||||
Transport CancelableTransport
|
||||
|
||||
// CheckRedirect specifies the policy for handling HTTP redirects.
|
||||
// If CheckRedirect is not nil, the Client calls it before
|
||||
// following an HTTP redirect. The sole argument is the number of
|
||||
// requests that have already been made. If CheckRedirect returns
|
||||
// an error, Client.Do will not make any further requests and return
|
||||
// the error back it to the caller.
|
||||
//
|
||||
// If CheckRedirect is nil, the Client uses its default policy,
|
||||
// which is to stop after 10 consecutive requests.
|
||||
CheckRedirect CheckRedirectFunc
|
||||
|
||||
// Username specifies the user credential to add as an authorization header
|
||||
Username string
|
||||
|
||||
// Password is the password for the specified user to add as an authorization header
|
||||
// to the request.
|
||||
Password string
|
||||
|
||||
// HeaderTimeoutPerRequest specifies the time limit to wait for response
|
||||
// header in a single request made by the Client. The timeout includes
|
||||
// connection time, any redirects, and header wait time.
|
||||
//
|
||||
// For non-watch GET request, server returns the response body immediately.
|
||||
// For PUT/POST/DELETE request, server will attempt to commit request
|
||||
// before responding, which is expected to take `100ms + 2 * RTT`.
|
||||
// For watch request, server returns the header immediately to notify Client
|
||||
// watch start. But if server is behind some kind of proxy, the response
|
||||
// header may be cached at proxy, and Client cannot rely on this behavior.
|
||||
//
|
||||
// Especially, wait request will ignore this timeout.
|
||||
//
|
||||
// One API call may send multiple requests to different etcd servers until it
|
||||
// succeeds. Use context of the API to specify the overall timeout.
|
||||
//
|
||||
// A HeaderTimeoutPerRequest of zero means no timeout.
|
||||
HeaderTimeoutPerRequest time.Duration
|
||||
|
||||
// SelectionMode is an EndpointSelectionMode enum that specifies the
|
||||
// policy for choosing the etcd cluster node to which requests are sent.
|
||||
SelectionMode EndpointSelectionMode
|
||||
}
|
||||
|
||||
func (cfg *Config) transport() CancelableTransport {
|
||||
if cfg.Transport == nil {
|
||||
return DefaultTransport
|
||||
}
|
||||
return cfg.Transport
|
||||
}
|
||||
|
||||
func (cfg *Config) checkRedirect() CheckRedirectFunc {
|
||||
if cfg.CheckRedirect == nil {
|
||||
return DefaultCheckRedirect
|
||||
}
|
||||
return cfg.CheckRedirect
|
||||
}
|
||||
|
||||
// CancelableTransport mimics net/http.Transport, but requires that
|
||||
// the object also support request cancellation.
|
||||
type CancelableTransport interface {
|
||||
http.RoundTripper
|
||||
CancelRequest(req *http.Request)
|
||||
}
|
||||
|
||||
type CheckRedirectFunc func(via int) error
|
||||
|
||||
// DefaultCheckRedirect follows up to 10 redirects, but no more.
|
||||
var DefaultCheckRedirect CheckRedirectFunc = func(via int) error {
|
||||
if via > 10 {
|
||||
return ErrTooManyRedirects
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
// Sync updates the internal cache of the etcd cluster's membership.
|
||||
Sync(context.Context) error
|
||||
|
||||
// AutoSync periodically calls Sync() every given interval.
|
||||
// The recommended sync interval is 10 seconds to 1 minute, which does
|
||||
// not bring too much overhead to server and makes client catch up the
|
||||
// cluster change in time.
|
||||
//
|
||||
// The example to use it:
|
||||
//
|
||||
// for {
|
||||
// err := client.AutoSync(ctx, 10*time.Second)
|
||||
// if err == context.DeadlineExceeded || err == context.Canceled {
|
||||
// break
|
||||
// }
|
||||
// log.Print(err)
|
||||
// }
|
||||
AutoSync(context.Context, time.Duration) error
|
||||
|
||||
// Endpoints returns a copy of the current set of API endpoints used
|
||||
// by Client to resolve HTTP requests. If Sync has ever been called,
|
||||
// this may differ from the initial Endpoints provided in the Config.
|
||||
Endpoints() []string
|
||||
|
||||
// SetEndpoints sets the set of API endpoints used by Client to resolve
|
||||
// HTTP requests. If the given endpoints are not valid, an error will be
|
||||
// returned
|
||||
SetEndpoints(eps []string) error
|
||||
|
||||
// GetVersion retrieves the current etcd server and cluster version
|
||||
GetVersion(ctx context.Context) (*version.Versions, error)
|
||||
|
||||
httpClient
|
||||
}
|
||||
|
||||
func New(cfg Config) (Client, error) {
|
||||
c := &httpClusterClient{
|
||||
clientFactory: newHTTPClientFactory(cfg.transport(), cfg.checkRedirect(), cfg.HeaderTimeoutPerRequest),
|
||||
rand: rand.New(rand.NewSource(int64(time.Now().Nanosecond()))),
|
||||
selectionMode: cfg.SelectionMode,
|
||||
}
|
||||
if cfg.Username != "" {
|
||||
c.credentials = &credentials{
|
||||
username: cfg.Username,
|
||||
password: cfg.Password,
|
||||
}
|
||||
}
|
||||
if err := c.SetEndpoints(cfg.Endpoints); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type httpClient interface {
|
||||
Do(context.Context, httpAction) (*http.Response, []byte, error)
|
||||
}
|
||||
|
||||
func newHTTPClientFactory(tr CancelableTransport, cr CheckRedirectFunc, headerTimeout time.Duration) httpClientFactory {
|
||||
return func(ep url.URL) httpClient {
|
||||
return &redirectFollowingHTTPClient{
|
||||
checkRedirect: cr,
|
||||
client: &simpleHTTPClient{
|
||||
transport: tr,
|
||||
endpoint: ep,
|
||||
headerTimeout: headerTimeout,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type credentials struct {
|
||||
username string
|
||||
password string
|
||||
}
|
||||
|
||||
type httpClientFactory func(url.URL) httpClient
|
||||
|
||||
type httpAction interface {
|
||||
HTTPRequest(url.URL) *http.Request
|
||||
}
|
||||
|
||||
type httpClusterClient struct {
|
||||
clientFactory httpClientFactory
|
||||
endpoints []url.URL
|
||||
pinned int
|
||||
credentials *credentials
|
||||
sync.RWMutex
|
||||
rand *rand.Rand
|
||||
selectionMode EndpointSelectionMode
|
||||
}
|
||||
|
||||
func (c *httpClusterClient) getLeaderEndpoint(ctx context.Context, eps []url.URL) (string, error) {
|
||||
ceps := make([]url.URL, len(eps))
|
||||
copy(ceps, eps)
|
||||
|
||||
// To perform a lookup on the new endpoint list without using the current
|
||||
// client, we'll copy it
|
||||
clientCopy := &httpClusterClient{
|
||||
clientFactory: c.clientFactory,
|
||||
credentials: c.credentials,
|
||||
rand: c.rand,
|
||||
|
||||
pinned: 0,
|
||||
endpoints: ceps,
|
||||
}
|
||||
|
||||
mAPI := NewMembersAPI(clientCopy)
|
||||
leader, err := mAPI.Leader(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(leader.ClientURLs) == 0 {
|
||||
return "", ErrNoLeaderEndpoint
|
||||
}
|
||||
|
||||
return leader.ClientURLs[0], nil // TODO: how to handle multiple client URLs?
|
||||
}
|
||||
|
||||
func (c *httpClusterClient) parseEndpoints(eps []string) ([]url.URL, error) {
|
||||
if len(eps) == 0 {
|
||||
return []url.URL{}, ErrNoEndpoints
|
||||
}
|
||||
|
||||
neps := make([]url.URL, len(eps))
|
||||
for i, ep := range eps {
|
||||
u, err := url.Parse(ep)
|
||||
if err != nil {
|
||||
return []url.URL{}, err
|
||||
}
|
||||
neps[i] = *u
|
||||
}
|
||||
return neps, nil
|
||||
}
|
||||
|
||||
func (c *httpClusterClient) SetEndpoints(eps []string) error {
|
||||
neps, err := c.parseEndpoints(eps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.endpoints = shuffleEndpoints(c.rand, neps)
|
||||
// We're not doing anything for PrioritizeLeader here. This is
|
||||
// due to not having a context meaning we can't call getLeaderEndpoint
|
||||
// However, if you're using PrioritizeLeader, you've already been told
|
||||
// to regularly call sync, where we do have a ctx, and can figure the
|
||||
// leader. PrioritizeLeader is also quite a loose guarantee, so deal
|
||||
// with it
|
||||
c.pinned = 0
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *httpClusterClient) Do(ctx context.Context, act httpAction) (*http.Response, []byte, error) {
|
||||
action := act
|
||||
c.RLock()
|
||||
leps := len(c.endpoints)
|
||||
eps := make([]url.URL, leps)
|
||||
n := copy(eps, c.endpoints)
|
||||
pinned := c.pinned
|
||||
|
||||
if c.credentials != nil {
|
||||
action = &authedAction{
|
||||
act: act,
|
||||
credentials: *c.credentials,
|
||||
}
|
||||
}
|
||||
c.RUnlock()
|
||||
|
||||
if leps == 0 {
|
||||
return nil, nil, ErrNoEndpoints
|
||||
}
|
||||
|
||||
if leps != n {
|
||||
return nil, nil, errors.New("unable to pick endpoint: copy failed")
|
||||
}
|
||||
|
||||
var resp *http.Response
|
||||
var body []byte
|
||||
var err error
|
||||
cerr := &ClusterError{}
|
||||
isOneShot := ctx.Value(&oneShotCtxValue) != nil
|
||||
|
||||
for i := pinned; i < leps+pinned; i++ {
|
||||
k := i % leps
|
||||
hc := c.clientFactory(eps[k])
|
||||
resp, body, err = hc.Do(ctx, action)
|
||||
if err != nil {
|
||||
cerr.Errors = append(cerr.Errors, err)
|
||||
if errors.Is(err, ctx.Err()) {
|
||||
return nil, nil, ctx.Err()
|
||||
}
|
||||
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
||||
return nil, nil, err
|
||||
}
|
||||
} else if resp.StatusCode/100 == 5 {
|
||||
switch resp.StatusCode {
|
||||
case http.StatusInternalServerError, http.StatusServiceUnavailable:
|
||||
// TODO: make sure this is a no leader response
|
||||
cerr.Errors = append(cerr.Errors, fmt.Errorf("client: etcd member %s has no leader", eps[k].String()))
|
||||
default:
|
||||
cerr.Errors = append(cerr.Errors, fmt.Errorf("client: etcd member %s returns server error [%s]", eps[k].String(), http.StatusText(resp.StatusCode)))
|
||||
}
|
||||
err = cerr.Errors[0]
|
||||
}
|
||||
if err != nil {
|
||||
if !isOneShot {
|
||||
continue
|
||||
}
|
||||
c.Lock()
|
||||
c.pinned = (k + 1) % leps
|
||||
c.Unlock()
|
||||
return nil, nil, err
|
||||
}
|
||||
if k != pinned {
|
||||
c.Lock()
|
||||
c.pinned = k
|
||||
c.Unlock()
|
||||
}
|
||||
return resp, body, nil
|
||||
}
|
||||
|
||||
return nil, nil, cerr
|
||||
}
|
||||
|
||||
func (c *httpClusterClient) Endpoints() []string {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
eps := make([]string, len(c.endpoints))
|
||||
for i, ep := range c.endpoints {
|
||||
eps[i] = ep.String()
|
||||
}
|
||||
|
||||
return eps
|
||||
}
|
||||
|
||||
func (c *httpClusterClient) Sync(ctx context.Context) error {
|
||||
mAPI := NewMembersAPI(c)
|
||||
ms, err := mAPI.List(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var eps []string
|
||||
for _, m := range ms {
|
||||
eps = append(eps, m.ClientURLs...)
|
||||
}
|
||||
|
||||
neps, err := c.parseEndpoints(eps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
npin := 0
|
||||
|
||||
switch c.selectionMode {
|
||||
case EndpointSelectionRandom:
|
||||
c.RLock()
|
||||
eq := endpointsEqual(c.endpoints, neps)
|
||||
c.RUnlock()
|
||||
|
||||
if eq {
|
||||
return nil
|
||||
}
|
||||
// When items in the endpoint list changes, we choose a new pin
|
||||
neps = shuffleEndpoints(c.rand, neps)
|
||||
case EndpointSelectionPrioritizeLeader:
|
||||
nle, err := c.getLeaderEndpoint(ctx, neps)
|
||||
if err != nil {
|
||||
return ErrNoLeaderEndpoint
|
||||
}
|
||||
|
||||
for i, n := range neps {
|
||||
if n.String() == nle {
|
||||
npin = i
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("invalid endpoint selection mode: %d", c.selectionMode)
|
||||
}
|
||||
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
c.endpoints = neps
|
||||
c.pinned = npin
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *httpClusterClient) AutoSync(ctx context.Context, interval time.Duration) error {
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
err := c.Sync(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *httpClusterClient) GetVersion(ctx context.Context) (*version.Versions, error) {
|
||||
act := &getAction{Prefix: "/version"}
|
||||
|
||||
resp, body, err := c.Do(ctx, act)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK:
|
||||
if len(body) == 0 {
|
||||
return nil, ErrEmptyBody
|
||||
}
|
||||
var vresp version.Versions
|
||||
if err := json.Unmarshal(body, &vresp); err != nil {
|
||||
return nil, ErrInvalidJSON
|
||||
}
|
||||
return &vresp, nil
|
||||
default:
|
||||
var etcdErr Error
|
||||
if err := json.Unmarshal(body, &etcdErr); err != nil {
|
||||
return nil, ErrInvalidJSON
|
||||
}
|
||||
return nil, etcdErr
|
||||
}
|
||||
}
|
||||
|
||||
type roundTripResponse struct {
|
||||
resp *http.Response
|
||||
err error
|
||||
}
|
||||
|
||||
type simpleHTTPClient struct {
|
||||
transport CancelableTransport
|
||||
endpoint url.URL
|
||||
headerTimeout time.Duration
|
||||
}
|
||||
|
||||
// ErrNoRequest indicates that the HTTPRequest object could not be found
|
||||
// or was nil. No processing could continue.
|
||||
var ErrNoRequest = errors.New("no HTTPRequest was available")
|
||||
|
||||
func (c *simpleHTTPClient) Do(ctx context.Context, act httpAction) (*http.Response, []byte, error) {
|
||||
req := act.HTTPRequest(c.endpoint)
|
||||
if req == nil {
|
||||
return nil, nil, ErrNoRequest
|
||||
}
|
||||
|
||||
if err := printcURL(req); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
isWait := false
|
||||
if req.URL != nil {
|
||||
ws := req.URL.Query().Get("wait")
|
||||
if len(ws) != 0 {
|
||||
var err error
|
||||
isWait, err = strconv.ParseBool(ws)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("wrong wait value %s (%w for %+v)", ws, err, req)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var hctx context.Context
|
||||
var hcancel context.CancelFunc
|
||||
if !isWait && c.headerTimeout > 0 {
|
||||
hctx, hcancel = context.WithTimeout(ctx, c.headerTimeout)
|
||||
} else {
|
||||
hctx, hcancel = context.WithCancel(ctx)
|
||||
}
|
||||
defer hcancel()
|
||||
|
||||
reqcancel := requestCanceler(req)
|
||||
|
||||
rtchan := make(chan roundTripResponse, 1)
|
||||
go func() {
|
||||
resp, err := c.transport.RoundTrip(req)
|
||||
rtchan <- roundTripResponse{resp: resp, err: err}
|
||||
close(rtchan)
|
||||
}()
|
||||
|
||||
var resp *http.Response
|
||||
var err error
|
||||
|
||||
select {
|
||||
case rtresp := <-rtchan:
|
||||
resp, err = rtresp.resp, rtresp.err
|
||||
case <-hctx.Done():
|
||||
// cancel and wait for request to actually exit before continuing
|
||||
reqcancel()
|
||||
rtresp := <-rtchan
|
||||
resp = rtresp.resp
|
||||
switch {
|
||||
case ctx.Err() != nil:
|
||||
err = ctx.Err()
|
||||
case hctx.Err() != nil:
|
||||
err = fmt.Errorf("client: endpoint %s exceeded header timeout", c.endpoint.String())
|
||||
default:
|
||||
panic("failed to get error from context")
|
||||
}
|
||||
}
|
||||
|
||||
// always check for resp nil-ness to deal with possible
|
||||
// race conditions between channels above
|
||||
defer func() {
|
||||
if resp != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var body []byte
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
if resp != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
<-done
|
||||
return nil, nil, ctx.Err()
|
||||
case <-done:
|
||||
}
|
||||
|
||||
return resp, body, err
|
||||
}
|
||||
|
||||
type authedAction struct {
|
||||
act httpAction
|
||||
credentials credentials
|
||||
}
|
||||
|
||||
func (a *authedAction) HTTPRequest(url url.URL) *http.Request {
|
||||
r := a.act.HTTPRequest(url)
|
||||
r.SetBasicAuth(a.credentials.username, a.credentials.password)
|
||||
return r
|
||||
}
|
||||
|
||||
type redirectFollowingHTTPClient struct {
|
||||
client httpClient
|
||||
checkRedirect CheckRedirectFunc
|
||||
}
|
||||
|
||||
func (r *redirectFollowingHTTPClient) Do(ctx context.Context, act httpAction) (*http.Response, []byte, error) {
|
||||
next := act
|
||||
for i := 0; i < 100; i++ {
|
||||
if i > 0 {
|
||||
if err := r.checkRedirect(i); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
resp, body, err := r.client.Do(ctx, next)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp.StatusCode/100 == 3 {
|
||||
hdr := resp.Header.Get("Location")
|
||||
if hdr == "" {
|
||||
return nil, nil, errors.New("location header not set")
|
||||
}
|
||||
loc, err := url.Parse(hdr)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("location header not valid URL: %s", hdr)
|
||||
}
|
||||
next = &redirectedHTTPAction{
|
||||
action: act,
|
||||
location: *loc,
|
||||
}
|
||||
continue
|
||||
}
|
||||
return resp, body, nil
|
||||
}
|
||||
|
||||
return nil, nil, errTooManyRedirectChecks
|
||||
}
|
||||
|
||||
type redirectedHTTPAction struct {
|
||||
action httpAction
|
||||
location url.URL
|
||||
}
|
||||
|
||||
func (r *redirectedHTTPAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
orig := r.action.HTTPRequest(ep)
|
||||
orig.URL = &r.location
|
||||
return orig
|
||||
}
|
||||
|
||||
func shuffleEndpoints(r *rand.Rand, eps []url.URL) []url.URL {
|
||||
// copied from Go 1.9<= rand.Rand.Perm
|
||||
n := len(eps)
|
||||
p := make([]int, n)
|
||||
for i := 0; i < n; i++ {
|
||||
j := r.Intn(i + 1)
|
||||
p[i] = p[j]
|
||||
p[j] = i
|
||||
}
|
||||
neps := make([]url.URL, n)
|
||||
for i, k := range p {
|
||||
neps[i] = eps[k]
|
||||
}
|
||||
return neps
|
||||
}
|
||||
|
||||
func endpointsEqual(left, right []url.URL) bool {
|
||||
if len(left) != len(right) {
|
||||
return false
|
||||
}
|
||||
|
||||
sLeft := make([]string, len(left))
|
||||
sRight := make([]string, len(right))
|
||||
for i, l := range left {
|
||||
sLeft[i] = l.String()
|
||||
}
|
||||
for i, r := range right {
|
||||
sRight[i] = r.String()
|
||||
}
|
||||
|
||||
sort.Strings(sLeft)
|
||||
sort.Strings(sRight)
|
||||
for i := range sLeft {
|
||||
if sLeft[i] != sRight[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,68 +0,0 @@
|
|||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
var cURLDebug = false
|
||||
|
||||
func EnablecURLDebug() {
|
||||
cURLDebug = true
|
||||
}
|
||||
|
||||
func DisablecURLDebug() {
|
||||
cURLDebug = false
|
||||
}
|
||||
|
||||
// printcURL prints the cURL equivalent request to stderr.
|
||||
// It returns an error if the body of the request cannot
|
||||
// be read.
|
||||
// The caller MUST cancel the request if there is an error.
|
||||
func printcURL(req *http.Request) error {
|
||||
if !cURLDebug {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
command string
|
||||
b []byte
|
||||
err error
|
||||
)
|
||||
|
||||
if req.URL != nil {
|
||||
command = fmt.Sprintf("curl -X %s %s", req.Method, req.URL.String())
|
||||
}
|
||||
|
||||
if req.Body != nil {
|
||||
b, err = io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
command += fmt.Sprintf(" -d %q", string(b))
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "cURL Command: %q\n", command)
|
||||
|
||||
// reset body
|
||||
body := bytes.NewBuffer(b)
|
||||
req.Body = io.NopCloser(body)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"go.etcd.io/etcd/client/pkg/v3/srv"
|
||||
)
|
||||
|
||||
// Discoverer is an interface that wraps the Discover method.
|
||||
type Discoverer interface {
|
||||
// Discover looks up the etcd servers for the domain.
|
||||
Discover(domain string, serviceName string) ([]string, error)
|
||||
}
|
||||
|
||||
type srvDiscover struct{}
|
||||
|
||||
// NewSRVDiscover constructs a new Discoverer that uses the stdlib to lookup SRV records.
|
||||
func NewSRVDiscover() Discoverer {
|
||||
return &srvDiscover{}
|
||||
}
|
||||
|
||||
func (d *srvDiscover) Discover(domain string, serviceName string) ([]string, error) {
|
||||
srvs, err := srv.GetClient("etcd-client", domain, serviceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return srvs.Endpoints, nil
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/*
|
||||
Package client provides bindings for the etcd APIs.
|
||||
|
||||
Create a Config and exchange it for a Client:
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"context"
|
||||
|
||||
"go.etcd.io/etcd/client/v2"
|
||||
)
|
||||
|
||||
cfg := client.Config{
|
||||
Endpoints: []string{"http://127.0.0.1:2379"},
|
||||
Transport: DefaultTransport,
|
||||
}
|
||||
|
||||
c, err := client.New(cfg)
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
Clients are safe for concurrent use by multiple goroutines.
|
||||
|
||||
Create a KeysAPI using the Client, then use it to interact with etcd:
|
||||
|
||||
kAPI := client.NewKeysAPI(c)
|
||||
|
||||
// create a new key /foo with the value "bar"
|
||||
_, err = kAPI.Create(context.Background(), "/foo", "bar")
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
// delete the newly created key only if the value is still "bar"
|
||||
_, err = kAPI.Delete(context.Background(), "/foo", &DeleteOptions{PrevValue: "bar"})
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
Use a custom context to set timeouts on your operations:
|
||||
|
||||
import "time"
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// set a new key, ignoring its previous state
|
||||
_, err := kAPI.Set(ctx, "/ping", "pong", nil)
|
||||
if err != nil {
|
||||
if err == context.DeadlineExceeded {
|
||||
// request took longer than 5s
|
||||
} else {
|
||||
// handle error
|
||||
}
|
||||
}
|
||||
*/
|
||||
package client
|
|
@ -1,34 +0,0 @@
|
|||
module go.etcd.io/etcd/client/v2
|
||||
|
||||
go 1.23
|
||||
|
||||
toolchain go1.23.6
|
||||
|
||||
require (
|
||||
github.com/stretchr/testify v1.10.0
|
||||
go.etcd.io/etcd/api/v3 v3.6.0-alpha.0
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0
|
||||
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
go.etcd.io/etcd/api/v3 => ./../../../api
|
||||
go.etcd.io/etcd/client/pkg/v3 => ./../../pkg
|
||||
)
|
||||
|
||||
// Bad imports are sometimes causing attempts to pull that code.
|
||||
// This makes the error more explicit.
|
||||
replace (
|
||||
go.etcd.io/etcd => ./FORBIDDEN_DEPENDENCY
|
||||
go.etcd.io/etcd/pkg/v3 => ./FORBIDDED_DEPENDENCY
|
||||
go.etcd.io/etcd/tests/v3 => ./FORBIDDEN_DEPENDENCY
|
||||
go.etcd.io/etcd/v3 => ./FORBIDDEN_DEPENDENCY
|
||||
)
|
|
@ -1,22 +0,0 @@
|
|||
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
||||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
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/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s=
|
||||
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs=
|
|
@ -1,678 +0,0 @@
|
|||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
kjson "sigs.k8s.io/json"
|
||||
|
||||
"go.etcd.io/etcd/client/pkg/v3/pathutil"
|
||||
)
|
||||
|
||||
const (
|
||||
ErrorCodeKeyNotFound = 100
|
||||
ErrorCodeTestFailed = 101
|
||||
ErrorCodeNotFile = 102
|
||||
ErrorCodeNotDir = 104
|
||||
ErrorCodeNodeExist = 105
|
||||
ErrorCodeRootROnly = 107
|
||||
ErrorCodeDirNotEmpty = 108
|
||||
ErrorCodeUnauthorized = 110
|
||||
|
||||
ErrorCodePrevValueRequired = 201
|
||||
ErrorCodeTTLNaN = 202
|
||||
ErrorCodeIndexNaN = 203
|
||||
ErrorCodeInvalidField = 209
|
||||
ErrorCodeInvalidForm = 210
|
||||
|
||||
ErrorCodeRaftInternal = 300
|
||||
ErrorCodeLeaderElect = 301
|
||||
|
||||
ErrorCodeWatcherCleared = 400
|
||||
ErrorCodeEventIndexCleared = 401
|
||||
)
|
||||
|
||||
type Error struct {
|
||||
Code int `json:"errorCode"`
|
||||
Message string `json:"message"`
|
||||
Cause string `json:"cause"`
|
||||
Index uint64 `json:"index"`
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return fmt.Sprintf("%v: %v (%v) [%v]", e.Code, e.Message, e.Cause, e.Index)
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidJSON = errors.New("client: response is invalid json. The endpoint is probably not valid etcd cluster endpoint")
|
||||
ErrEmptyBody = errors.New("client: response body is empty")
|
||||
)
|
||||
|
||||
// PrevExistType is used to define an existence condition when setting
|
||||
// or deleting Nodes.
|
||||
type PrevExistType string
|
||||
|
||||
const (
|
||||
PrevIgnore = PrevExistType("")
|
||||
PrevExist = PrevExistType("true")
|
||||
PrevNoExist = PrevExistType("false")
|
||||
)
|
||||
|
||||
var defaultV2KeysPrefix = "/v2/keys"
|
||||
|
||||
// NewKeysAPI builds a KeysAPI that interacts with etcd's key-value
|
||||
// API over HTTP.
|
||||
func NewKeysAPI(c Client) KeysAPI {
|
||||
return NewKeysAPIWithPrefix(c, defaultV2KeysPrefix)
|
||||
}
|
||||
|
||||
// NewKeysAPIWithPrefix acts like NewKeysAPI, but allows the caller
|
||||
// to provide a custom base URL path. This should only be used in
|
||||
// very rare cases.
|
||||
func NewKeysAPIWithPrefix(c Client, p string) KeysAPI {
|
||||
return &httpKeysAPI{
|
||||
client: c,
|
||||
prefix: p,
|
||||
}
|
||||
}
|
||||
|
||||
type KeysAPI interface {
|
||||
// Get retrieves a set of Nodes from etcd
|
||||
Get(ctx context.Context, key string, opts *GetOptions) (*Response, error)
|
||||
|
||||
// Set assigns a new value to a Node identified by a given key. The caller
|
||||
// may define a set of conditions in the SetOptions. If SetOptions.Dir=true
|
||||
// then value is ignored.
|
||||
Set(ctx context.Context, key, value string, opts *SetOptions) (*Response, error)
|
||||
|
||||
// Delete removes a Node identified by the given key, optionally destroying
|
||||
// all of its children as well. The caller may define a set of required
|
||||
// conditions in an DeleteOptions object.
|
||||
Delete(ctx context.Context, key string, opts *DeleteOptions) (*Response, error)
|
||||
|
||||
// Create is an alias for Set w/ PrevExist=false
|
||||
Create(ctx context.Context, key, value string) (*Response, error)
|
||||
|
||||
// CreateInOrder is used to atomically create in-order keys within the given directory.
|
||||
CreateInOrder(ctx context.Context, dir, value string, opts *CreateInOrderOptions) (*Response, error)
|
||||
|
||||
// Update is an alias for Set w/ PrevExist=true
|
||||
Update(ctx context.Context, key, value string) (*Response, error)
|
||||
|
||||
// Watcher builds a new Watcher targeted at a specific Node identified
|
||||
// by the given key. The Watcher may be configured at creation time
|
||||
// through a WatcherOptions object. The returned Watcher is designed
|
||||
// to emit events that happen to a Node, and optionally to its children.
|
||||
Watcher(key string, opts *WatcherOptions) Watcher
|
||||
}
|
||||
|
||||
type WatcherOptions struct {
|
||||
// AfterIndex defines the index after-which the Watcher should
|
||||
// start emitting events. For example, if a value of 5 is
|
||||
// provided, the first event will have an index >= 6.
|
||||
//
|
||||
// Setting AfterIndex to 0 (default) means that the Watcher
|
||||
// should start watching for events starting at the current
|
||||
// index, whatever that may be.
|
||||
AfterIndex uint64
|
||||
|
||||
// Recursive specifies whether or not the Watcher should emit
|
||||
// events that occur in children of the given keyspace. If set
|
||||
// to false (default), events will be limited to those that
|
||||
// occur for the exact key.
|
||||
Recursive bool
|
||||
}
|
||||
|
||||
type CreateInOrderOptions struct {
|
||||
// TTL defines a period of time after-which the Node should
|
||||
// expire and no longer exist. Values <= 0 are ignored. Given
|
||||
// that the zero-value is ignored, TTL cannot be used to set
|
||||
// a TTL of 0.
|
||||
TTL time.Duration
|
||||
}
|
||||
|
||||
type SetOptions struct {
|
||||
// PrevValue specifies what the current value of the Node must
|
||||
// be in order for the Set operation to succeed.
|
||||
//
|
||||
// Leaving this field empty means that the caller wishes to
|
||||
// ignore the current value of the Node. This cannot be used
|
||||
// to compare the Node's current value to an empty string.
|
||||
//
|
||||
// PrevValue is ignored if Dir=true
|
||||
PrevValue string
|
||||
|
||||
// PrevIndex indicates what the current ModifiedIndex of the
|
||||
// Node must be in order for the Set operation to succeed.
|
||||
//
|
||||
// If PrevIndex is set to 0 (default), no comparison is made.
|
||||
PrevIndex uint64
|
||||
|
||||
// PrevExist specifies whether the Node must currently exist
|
||||
// (PrevExist) or not (PrevNoExist). If the caller does not
|
||||
// care about existence, set PrevExist to PrevIgnore, or simply
|
||||
// leave it unset.
|
||||
PrevExist PrevExistType
|
||||
|
||||
// TTL defines a period of time after-which the Node should
|
||||
// expire and no longer exist. Values <= 0 are ignored. Given
|
||||
// that the zero-value is ignored, TTL cannot be used to set
|
||||
// a TTL of 0.
|
||||
TTL time.Duration
|
||||
|
||||
// Refresh set to true means a TTL value can be updated
|
||||
// without firing a watch or changing the node value. A
|
||||
// value must not be provided when refreshing a key.
|
||||
Refresh bool
|
||||
|
||||
// Dir specifies whether or not this Node should be created as a directory.
|
||||
Dir bool
|
||||
|
||||
// NoValueOnSuccess specifies whether the response contains the current value of the Node.
|
||||
// If set, the response will only contain the current value when the request fails.
|
||||
NoValueOnSuccess bool
|
||||
}
|
||||
|
||||
type GetOptions struct {
|
||||
// Recursive defines whether or not all children of the Node
|
||||
// should be returned.
|
||||
Recursive bool
|
||||
|
||||
// Sort instructs the server whether or not to sort the Nodes.
|
||||
// If true, the Nodes are sorted alphabetically by key in
|
||||
// ascending order (A to z). If false (default), the Nodes will
|
||||
// not be sorted and the ordering used should not be considered
|
||||
// predictable.
|
||||
Sort bool
|
||||
|
||||
// Quorum specifies whether it gets the latest committed value that
|
||||
// has been applied in quorum of members, which ensures external
|
||||
// consistency (or linearizability).
|
||||
Quorum bool
|
||||
}
|
||||
|
||||
type DeleteOptions struct {
|
||||
// PrevValue specifies what the current value of the Node must
|
||||
// be in order for the Delete operation to succeed.
|
||||
//
|
||||
// Leaving this field empty means that the caller wishes to
|
||||
// ignore the current value of the Node. This cannot be used
|
||||
// to compare the Node's current value to an empty string.
|
||||
PrevValue string
|
||||
|
||||
// PrevIndex indicates what the current ModifiedIndex of the
|
||||
// Node must be in order for the Delete operation to succeed.
|
||||
//
|
||||
// If PrevIndex is set to 0 (default), no comparison is made.
|
||||
PrevIndex uint64
|
||||
|
||||
// Recursive defines whether or not all children of the Node
|
||||
// should be deleted. If set to true, all children of the Node
|
||||
// identified by the given key will be deleted. If left unset
|
||||
// or explicitly set to false, only a single Node will be
|
||||
// deleted.
|
||||
Recursive bool
|
||||
|
||||
// Dir specifies whether or not this Node should be removed as a directory.
|
||||
Dir bool
|
||||
}
|
||||
|
||||
type Watcher interface {
|
||||
// Next blocks until an etcd event occurs, then returns a Response
|
||||
// representing that event. The behavior of Next depends on the
|
||||
// WatcherOptions used to construct the Watcher. Next is designed to
|
||||
// be called repeatedly, each time blocking until a subsequent event
|
||||
// is available.
|
||||
//
|
||||
// If the provided context is cancelled, Next will return a non-nil
|
||||
// error. Any other failures encountered while waiting for the next
|
||||
// event (connection issues, deserialization failures, etc) will
|
||||
// also result in a non-nil error.
|
||||
Next(context.Context) (*Response, error)
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
// Action is the name of the operation that occurred. Possible values
|
||||
// include get, set, delete, update, create, compareAndSwap,
|
||||
// compareAndDelete and expire.
|
||||
Action string `json:"action"`
|
||||
|
||||
// Node represents the state of the relevant etcd Node.
|
||||
Node *Node `json:"node"`
|
||||
|
||||
// PrevNode represents the previous state of the Node. PrevNode is non-nil
|
||||
// only if the Node existed before the action occurred and the action
|
||||
// caused a change to the Node.
|
||||
PrevNode *Node `json:"prevNode"`
|
||||
|
||||
// Index holds the cluster-level index at the time the Response was generated.
|
||||
// This index is not tied to the Node(s) contained in this Response.
|
||||
Index uint64 `json:"-"`
|
||||
|
||||
// ClusterID holds the cluster-level ID reported by the server. This
|
||||
// should be different for different etcd clusters.
|
||||
ClusterID string `json:"-"`
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
// Key represents the unique location of this Node (e.g. "/foo/bar").
|
||||
Key string `json:"key"`
|
||||
|
||||
// Dir reports whether node describes a directory.
|
||||
Dir bool `json:"dir,omitempty"`
|
||||
|
||||
// Value is the current data stored on this Node. If this Node
|
||||
// is a directory, Value will be empty.
|
||||
Value string `json:"value"`
|
||||
|
||||
// Nodes holds the children of this Node, only if this Node is a directory.
|
||||
// This slice of will be arbitrarily deep (children, grandchildren, great-
|
||||
// grandchildren, etc.) if a recursive Get or Watch request were made.
|
||||
Nodes Nodes `json:"nodes"`
|
||||
|
||||
// CreatedIndex is the etcd index at-which this Node was created.
|
||||
CreatedIndex uint64 `json:"createdIndex"`
|
||||
|
||||
// ModifiedIndex is the etcd index at-which this Node was last modified.
|
||||
ModifiedIndex uint64 `json:"modifiedIndex"`
|
||||
|
||||
// Expiration is the server side expiration time of the key.
|
||||
Expiration *time.Time `json:"expiration,omitempty"`
|
||||
|
||||
// TTL is the time to live of the key in second.
|
||||
TTL int64 `json:"ttl,omitempty"`
|
||||
}
|
||||
|
||||
func (n *Node) String() string {
|
||||
return fmt.Sprintf("{Key: %s, CreatedIndex: %d, ModifiedIndex: %d, TTL: %d}", n.Key, n.CreatedIndex, n.ModifiedIndex, n.TTL)
|
||||
}
|
||||
|
||||
// TTLDuration returns the Node's TTL as a time.Duration object
|
||||
func (n *Node) TTLDuration() time.Duration {
|
||||
return time.Duration(n.TTL) * time.Second
|
||||
}
|
||||
|
||||
type Nodes []*Node
|
||||
|
||||
// interfaces for sorting
|
||||
|
||||
func (ns Nodes) Len() int { return len(ns) }
|
||||
func (ns Nodes) Less(i, j int) bool { return ns[i].Key < ns[j].Key }
|
||||
func (ns Nodes) Swap(i, j int) { ns[i], ns[j] = ns[j], ns[i] }
|
||||
|
||||
type httpKeysAPI struct {
|
||||
client httpClient
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (k *httpKeysAPI) Set(ctx context.Context, key, val string, opts *SetOptions) (*Response, error) {
|
||||
act := &setAction{
|
||||
Prefix: k.prefix,
|
||||
Key: key,
|
||||
Value: val,
|
||||
}
|
||||
|
||||
if opts != nil {
|
||||
act.PrevValue = opts.PrevValue
|
||||
act.PrevIndex = opts.PrevIndex
|
||||
act.PrevExist = opts.PrevExist
|
||||
act.TTL = opts.TTL
|
||||
act.Refresh = opts.Refresh
|
||||
act.Dir = opts.Dir
|
||||
act.NoValueOnSuccess = opts.NoValueOnSuccess
|
||||
}
|
||||
|
||||
doCtx := ctx
|
||||
if act.PrevExist == PrevNoExist {
|
||||
doCtx = context.WithValue(doCtx, &oneShotCtxValue, &oneShotCtxValue)
|
||||
}
|
||||
resp, body, err := k.client.Do(doCtx, act)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return unmarshalHTTPResponse(resp.StatusCode, resp.Header, body)
|
||||
}
|
||||
|
||||
func (k *httpKeysAPI) Create(ctx context.Context, key, val string) (*Response, error) {
|
||||
return k.Set(ctx, key, val, &SetOptions{PrevExist: PrevNoExist})
|
||||
}
|
||||
|
||||
func (k *httpKeysAPI) CreateInOrder(ctx context.Context, dir, val string, opts *CreateInOrderOptions) (*Response, error) {
|
||||
act := &createInOrderAction{
|
||||
Prefix: k.prefix,
|
||||
Dir: dir,
|
||||
Value: val,
|
||||
}
|
||||
|
||||
if opts != nil {
|
||||
act.TTL = opts.TTL
|
||||
}
|
||||
|
||||
resp, body, err := k.client.Do(ctx, act)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return unmarshalHTTPResponse(resp.StatusCode, resp.Header, body)
|
||||
}
|
||||
|
||||
func (k *httpKeysAPI) Update(ctx context.Context, key, val string) (*Response, error) {
|
||||
return k.Set(ctx, key, val, &SetOptions{PrevExist: PrevExist})
|
||||
}
|
||||
|
||||
func (k *httpKeysAPI) Delete(ctx context.Context, key string, opts *DeleteOptions) (*Response, error) {
|
||||
act := &deleteAction{
|
||||
Prefix: k.prefix,
|
||||
Key: key,
|
||||
}
|
||||
|
||||
if opts != nil {
|
||||
act.PrevValue = opts.PrevValue
|
||||
act.PrevIndex = opts.PrevIndex
|
||||
act.Dir = opts.Dir
|
||||
act.Recursive = opts.Recursive
|
||||
}
|
||||
|
||||
doCtx := context.WithValue(ctx, &oneShotCtxValue, &oneShotCtxValue)
|
||||
resp, body, err := k.client.Do(doCtx, act)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return unmarshalHTTPResponse(resp.StatusCode, resp.Header, body)
|
||||
}
|
||||
|
||||
func (k *httpKeysAPI) Get(ctx context.Context, key string, opts *GetOptions) (*Response, error) {
|
||||
act := &getAction{
|
||||
Prefix: k.prefix,
|
||||
Key: key,
|
||||
}
|
||||
|
||||
if opts != nil {
|
||||
act.Recursive = opts.Recursive
|
||||
act.Sorted = opts.Sort
|
||||
act.Quorum = opts.Quorum
|
||||
}
|
||||
|
||||
resp, body, err := k.client.Do(ctx, act)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return unmarshalHTTPResponse(resp.StatusCode, resp.Header, body)
|
||||
}
|
||||
|
||||
func (k *httpKeysAPI) Watcher(key string, opts *WatcherOptions) Watcher {
|
||||
act := waitAction{
|
||||
Prefix: k.prefix,
|
||||
Key: key,
|
||||
}
|
||||
|
||||
if opts != nil {
|
||||
act.Recursive = opts.Recursive
|
||||
if opts.AfterIndex > 0 {
|
||||
act.WaitIndex = opts.AfterIndex + 1
|
||||
}
|
||||
}
|
||||
|
||||
return &httpWatcher{
|
||||
client: k.client,
|
||||
nextWait: act,
|
||||
}
|
||||
}
|
||||
|
||||
type httpWatcher struct {
|
||||
client httpClient
|
||||
nextWait waitAction
|
||||
}
|
||||
|
||||
func (hw *httpWatcher) Next(ctx context.Context) (*Response, error) {
|
||||
for {
|
||||
httpresp, body, err := hw.client.Do(ctx, &hw.nextWait)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := unmarshalHTTPResponse(httpresp.StatusCode, httpresp.Header, body)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrEmptyBody) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hw.nextWait.WaitIndex = resp.Node.ModifiedIndex + 1
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
|
||||
// v2KeysURL forms a URL representing the location of a key.
|
||||
// The endpoint argument represents the base URL of an etcd
|
||||
// server. The prefix is the path needed to route from the
|
||||
// provided endpoint's path to the root of the keys API
|
||||
// (typically "/v2/keys").
|
||||
func v2KeysURL(ep url.URL, prefix, key string) *url.URL {
|
||||
// We concatenate all parts together manually. We cannot use
|
||||
// path.Join because it does not reserve trailing slash.
|
||||
// We call CanonicalURLPath to further cleanup the path.
|
||||
if prefix != "" && prefix[0] != '/' {
|
||||
prefix = "/" + prefix
|
||||
}
|
||||
if key != "" && key[0] != '/' {
|
||||
key = "/" + key
|
||||
}
|
||||
ep.Path = pathutil.CanonicalURLPath(ep.Path + prefix + key)
|
||||
return &ep
|
||||
}
|
||||
|
||||
type getAction struct {
|
||||
Prefix string
|
||||
Key string
|
||||
Recursive bool
|
||||
Sorted bool
|
||||
Quorum bool
|
||||
}
|
||||
|
||||
func (g *getAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2KeysURL(ep, g.Prefix, g.Key)
|
||||
|
||||
params := u.Query()
|
||||
params.Set("recursive", strconv.FormatBool(g.Recursive))
|
||||
params.Set("sorted", strconv.FormatBool(g.Sorted))
|
||||
params.Set("quorum", strconv.FormatBool(g.Quorum))
|
||||
u.RawQuery = params.Encode()
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
return req
|
||||
}
|
||||
|
||||
type waitAction struct {
|
||||
Prefix string
|
||||
Key string
|
||||
WaitIndex uint64
|
||||
Recursive bool
|
||||
}
|
||||
|
||||
func (w *waitAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2KeysURL(ep, w.Prefix, w.Key)
|
||||
|
||||
params := u.Query()
|
||||
params.Set("wait", "true")
|
||||
params.Set("waitIndex", strconv.FormatUint(w.WaitIndex, 10))
|
||||
params.Set("recursive", strconv.FormatBool(w.Recursive))
|
||||
u.RawQuery = params.Encode()
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
return req
|
||||
}
|
||||
|
||||
type setAction struct {
|
||||
Prefix string
|
||||
Key string
|
||||
Value string
|
||||
PrevValue string
|
||||
PrevIndex uint64
|
||||
PrevExist PrevExistType
|
||||
TTL time.Duration
|
||||
Refresh bool
|
||||
Dir bool
|
||||
NoValueOnSuccess bool
|
||||
}
|
||||
|
||||
func (a *setAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2KeysURL(ep, a.Prefix, a.Key)
|
||||
|
||||
params := u.Query()
|
||||
form := url.Values{}
|
||||
|
||||
// we're either creating a directory or setting a key
|
||||
if a.Dir {
|
||||
params.Set("dir", strconv.FormatBool(a.Dir))
|
||||
} else {
|
||||
// These options are only valid for setting a key
|
||||
if a.PrevValue != "" {
|
||||
params.Set("prevValue", a.PrevValue)
|
||||
}
|
||||
form.Add("value", a.Value)
|
||||
}
|
||||
|
||||
// Options which apply to both setting a key and creating a dir
|
||||
if a.PrevIndex != 0 {
|
||||
params.Set("prevIndex", strconv.FormatUint(a.PrevIndex, 10))
|
||||
}
|
||||
if a.PrevExist != PrevIgnore {
|
||||
params.Set("prevExist", string(a.PrevExist))
|
||||
}
|
||||
if a.TTL > 0 {
|
||||
form.Add("ttl", strconv.FormatUint(uint64(a.TTL.Seconds()), 10))
|
||||
}
|
||||
|
||||
if a.Refresh {
|
||||
form.Add("refresh", "true")
|
||||
}
|
||||
if a.NoValueOnSuccess {
|
||||
params.Set("noValueOnSuccess", strconv.FormatBool(a.NoValueOnSuccess))
|
||||
}
|
||||
|
||||
u.RawQuery = params.Encode()
|
||||
body := strings.NewReader(form.Encode())
|
||||
|
||||
req, _ := http.NewRequest(http.MethodPut, u.String(), body)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
type deleteAction struct {
|
||||
Prefix string
|
||||
Key string
|
||||
PrevValue string
|
||||
PrevIndex uint64
|
||||
Dir bool
|
||||
Recursive bool
|
||||
}
|
||||
|
||||
func (a *deleteAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2KeysURL(ep, a.Prefix, a.Key)
|
||||
|
||||
params := u.Query()
|
||||
if a.PrevValue != "" {
|
||||
params.Set("prevValue", a.PrevValue)
|
||||
}
|
||||
if a.PrevIndex != 0 {
|
||||
params.Set("prevIndex", strconv.FormatUint(a.PrevIndex, 10))
|
||||
}
|
||||
if a.Dir {
|
||||
params.Set("dir", "true")
|
||||
}
|
||||
if a.Recursive {
|
||||
params.Set("recursive", "true")
|
||||
}
|
||||
u.RawQuery = params.Encode()
|
||||
|
||||
req, _ := http.NewRequest(http.MethodDelete, u.String(), nil)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
type createInOrderAction struct {
|
||||
Prefix string
|
||||
Dir string
|
||||
Value string
|
||||
TTL time.Duration
|
||||
}
|
||||
|
||||
func (a *createInOrderAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2KeysURL(ep, a.Prefix, a.Dir)
|
||||
|
||||
form := url.Values{}
|
||||
form.Add("value", a.Value)
|
||||
if a.TTL > 0 {
|
||||
form.Add("ttl", strconv.FormatUint(uint64(a.TTL.Seconds()), 10))
|
||||
}
|
||||
body := strings.NewReader(form.Encode())
|
||||
|
||||
req, _ := http.NewRequest(http.MethodPost, u.String(), body)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
return req
|
||||
}
|
||||
|
||||
func unmarshalHTTPResponse(code int, header http.Header, body []byte) (res *Response, err error) {
|
||||
switch code {
|
||||
case http.StatusOK, http.StatusCreated:
|
||||
if len(body) == 0 {
|
||||
return nil, ErrEmptyBody
|
||||
}
|
||||
res, err = unmarshalSuccessfulKeysResponse(header, body)
|
||||
default:
|
||||
err = unmarshalFailedKeysResponse(body)
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
func unmarshalSuccessfulKeysResponse(header http.Header, body []byte) (*Response, error) {
|
||||
var res Response
|
||||
err := kjson.UnmarshalCaseSensitivePreserveInts(body, &res)
|
||||
if err != nil {
|
||||
return nil, ErrInvalidJSON
|
||||
}
|
||||
if header.Get("X-Etcd-Index") != "" {
|
||||
res.Index, err = strconv.ParseUint(header.Get("X-Etcd-Index"), 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
res.ClusterID = header.Get("X-Etcd-Cluster-ID")
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func unmarshalFailedKeysResponse(body []byte) error {
|
||||
var etcdErr Error
|
||||
if err := json.Unmarshal(body, &etcdErr); err != nil {
|
||||
return ErrInvalidJSON
|
||||
}
|
||||
return etcdErr
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func createTestNode(size int) *Node {
|
||||
return &Node{
|
||||
Key: strings.Repeat("a", 30),
|
||||
Value: strings.Repeat("a", size),
|
||||
CreatedIndex: 123456,
|
||||
ModifiedIndex: 123456,
|
||||
TTL: 123456789,
|
||||
}
|
||||
}
|
||||
|
||||
func createTestNodeWithChildren(children, size int) *Node {
|
||||
node := createTestNode(size)
|
||||
for i := 0; i < children; i++ {
|
||||
node.Nodes = append(node.Nodes, createTestNode(size))
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
func createTestResponse(children, size int) *Response {
|
||||
return &Response{
|
||||
Action: "aaaaa",
|
||||
Node: createTestNodeWithChildren(children, size),
|
||||
PrevNode: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkResponseUnmarshalling(b *testing.B, children, size int) {
|
||||
b.Helper()
|
||||
header := http.Header{}
|
||||
header.Add("X-Etcd-Index", "123456")
|
||||
response := createTestResponse(children, size)
|
||||
body, err := json.Marshal(response)
|
||||
require.NoError(b, err)
|
||||
|
||||
b.ResetTimer()
|
||||
newResponse := new(Response)
|
||||
for i := 0; i < b.N; i++ {
|
||||
if newResponse, err = unmarshalSuccessfulKeysResponse(header, body); err != nil {
|
||||
b.Errorf("error unmarshalling response (%v)", err)
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(response.Node, newResponse.Node) {
|
||||
b.Errorf("Unexpected difference in a parsed response: \n%+v\n%+v", response, newResponse)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSmallResponseUnmarshal(b *testing.B) {
|
||||
benchmarkResponseUnmarshalling(b, 30, 20)
|
||||
}
|
||||
|
||||
func BenchmarkManySmallResponseUnmarshal(b *testing.B) {
|
||||
benchmarkResponseUnmarshalling(b, 3000, 20)
|
||||
}
|
||||
|
||||
func BenchmarkMediumResponseUnmarshal(b *testing.B) {
|
||||
benchmarkResponseUnmarshalling(b, 300, 200)
|
||||
}
|
||||
|
||||
func BenchmarkLargeResponseUnmarshal(b *testing.B) {
|
||||
benchmarkResponseUnmarshalling(b, 3000, 2000)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,303 +0,0 @@
|
|||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
"go.etcd.io/etcd/client/pkg/v3/types"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultV2MembersPrefix = "/v2/members"
|
||||
defaultLeaderSuffix = "/leader"
|
||||
)
|
||||
|
||||
type Member struct {
|
||||
// ID is the unique identifier of this Member.
|
||||
ID string `json:"id"`
|
||||
|
||||
// Name is a human-readable, non-unique identifier of this Member.
|
||||
Name string `json:"name"`
|
||||
|
||||
// PeerURLs represents the HTTP(S) endpoints this Member uses to
|
||||
// participate in etcd's consensus protocol.
|
||||
PeerURLs []string `json:"peerURLs"`
|
||||
|
||||
// ClientURLs represents the HTTP(S) endpoints on which this Member
|
||||
// serves its client-facing APIs.
|
||||
ClientURLs []string `json:"clientURLs"`
|
||||
}
|
||||
|
||||
type memberCollection []Member
|
||||
|
||||
func (c *memberCollection) UnmarshalJSON(data []byte) error {
|
||||
d := struct {
|
||||
Members []Member
|
||||
}{}
|
||||
|
||||
if err := json.Unmarshal(data, &d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.Members == nil {
|
||||
*c = make([]Member, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
*c = d.Members
|
||||
return nil
|
||||
}
|
||||
|
||||
type memberCreateOrUpdateRequest struct {
|
||||
PeerURLs types.URLs
|
||||
}
|
||||
|
||||
func (m *memberCreateOrUpdateRequest) MarshalJSON() ([]byte, error) {
|
||||
s := struct {
|
||||
PeerURLs []string `json:"peerURLs"`
|
||||
}{
|
||||
PeerURLs: make([]string, len(m.PeerURLs)),
|
||||
}
|
||||
|
||||
for i, u := range m.PeerURLs {
|
||||
s.PeerURLs[i] = u.String()
|
||||
}
|
||||
|
||||
return json.Marshal(&s)
|
||||
}
|
||||
|
||||
// NewMembersAPI constructs a new MembersAPI that uses HTTP to
|
||||
// interact with etcd's membership API.
|
||||
func NewMembersAPI(c Client) MembersAPI {
|
||||
return &httpMembersAPI{
|
||||
client: c,
|
||||
}
|
||||
}
|
||||
|
||||
type MembersAPI interface {
|
||||
// List enumerates the current cluster membership.
|
||||
List(ctx context.Context) ([]Member, error)
|
||||
|
||||
// Add instructs etcd to accept a new Member into the cluster.
|
||||
Add(ctx context.Context, peerURL string) (*Member, error)
|
||||
|
||||
// Remove demotes an existing Member out of the cluster.
|
||||
Remove(ctx context.Context, mID string) error
|
||||
|
||||
// Update instructs etcd to update an existing Member in the cluster.
|
||||
Update(ctx context.Context, mID string, peerURLs []string) error
|
||||
|
||||
// Leader gets current leader of the cluster
|
||||
Leader(ctx context.Context) (*Member, error)
|
||||
}
|
||||
|
||||
type httpMembersAPI struct {
|
||||
client httpClient
|
||||
}
|
||||
|
||||
func (m *httpMembersAPI) List(ctx context.Context) ([]Member, error) {
|
||||
req := &membersAPIActionList{}
|
||||
resp, body, err := m.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var mCollection memberCollection
|
||||
if err := json.Unmarshal(body, &mCollection); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mCollection, nil
|
||||
}
|
||||
|
||||
func (m *httpMembersAPI) Add(ctx context.Context, peerURL string) (*Member, error) {
|
||||
urls, err := types.NewURLs([]string{peerURL})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := &membersAPIActionAdd{peerURLs: urls}
|
||||
resp, body, err := m.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := assertStatusCode(resp.StatusCode, http.StatusCreated, http.StatusConflict); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
var merr membersError
|
||||
if err := json.Unmarshal(body, &merr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, merr
|
||||
}
|
||||
|
||||
var memb Member
|
||||
if err := json.Unmarshal(body, &memb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &memb, nil
|
||||
}
|
||||
|
||||
func (m *httpMembersAPI) Update(ctx context.Context, memberID string, peerURLs []string) error {
|
||||
urls, err := types.NewURLs(peerURLs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := &membersAPIActionUpdate{peerURLs: urls, memberID: memberID}
|
||||
resp, body, err := m.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := assertStatusCode(resp.StatusCode, http.StatusNoContent, http.StatusNotFound, http.StatusConflict); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
var merr membersError
|
||||
if err := json.Unmarshal(body, &merr); err != nil {
|
||||
return err
|
||||
}
|
||||
return merr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *httpMembersAPI) Remove(ctx context.Context, memberID string) error {
|
||||
req := &membersAPIActionRemove{memberID: memberID}
|
||||
resp, _, err := m.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return assertStatusCode(resp.StatusCode, http.StatusNoContent, http.StatusGone)
|
||||
}
|
||||
|
||||
func (m *httpMembersAPI) Leader(ctx context.Context) (*Member, error) {
|
||||
req := &membersAPIActionLeader{}
|
||||
resp, body, err := m.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var leader Member
|
||||
if err := json.Unmarshal(body, &leader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &leader, nil
|
||||
}
|
||||
|
||||
type membersAPIActionList struct{}
|
||||
|
||||
func (l *membersAPIActionList) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2MembersURL(ep)
|
||||
req, _ := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
return req
|
||||
}
|
||||
|
||||
type membersAPIActionRemove struct {
|
||||
memberID string
|
||||
}
|
||||
|
||||
func (d *membersAPIActionRemove) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2MembersURL(ep)
|
||||
u.Path = path.Join(u.Path, d.memberID)
|
||||
req, _ := http.NewRequest(http.MethodDelete, u.String(), nil)
|
||||
return req
|
||||
}
|
||||
|
||||
type membersAPIActionAdd struct {
|
||||
peerURLs types.URLs
|
||||
}
|
||||
|
||||
func (a *membersAPIActionAdd) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2MembersURL(ep)
|
||||
m := memberCreateOrUpdateRequest{PeerURLs: a.peerURLs}
|
||||
b, _ := json.Marshal(&m)
|
||||
req, _ := http.NewRequest(http.MethodPost, u.String(), bytes.NewReader(b))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
return req
|
||||
}
|
||||
|
||||
type membersAPIActionUpdate struct {
|
||||
memberID string
|
||||
peerURLs types.URLs
|
||||
}
|
||||
|
||||
func (a *membersAPIActionUpdate) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2MembersURL(ep)
|
||||
m := memberCreateOrUpdateRequest{PeerURLs: a.peerURLs}
|
||||
u.Path = path.Join(u.Path, a.memberID)
|
||||
b, _ := json.Marshal(&m)
|
||||
req, _ := http.NewRequest(http.MethodPut, u.String(), bytes.NewReader(b))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
return req
|
||||
}
|
||||
|
||||
func assertStatusCode(got int, want ...int) (err error) {
|
||||
for _, w := range want {
|
||||
if w == got {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("unexpected status code %d", got)
|
||||
}
|
||||
|
||||
type membersAPIActionLeader struct{}
|
||||
|
||||
func (l *membersAPIActionLeader) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2MembersURL(ep)
|
||||
u.Path = path.Join(u.Path, defaultLeaderSuffix)
|
||||
req, _ := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
return req
|
||||
}
|
||||
|
||||
// v2MembersURL add the necessary path to the provided endpoint
|
||||
// to route requests to the default v2 members API.
|
||||
func v2MembersURL(ep url.URL) *url.URL {
|
||||
ep.Path = path.Join(ep.Path, defaultV2MembersPrefix)
|
||||
return &ep
|
||||
}
|
||||
|
||||
type membersError struct {
|
||||
Message string `json:"message"`
|
||||
Code int `json:"-"`
|
||||
}
|
||||
|
||||
func (e membersError) Error() string {
|
||||
return e.Message
|
||||
}
|
|
@ -1,594 +0,0 @@
|
|||
// Copyright 2015 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.etcd.io/etcd/client/pkg/v3/types"
|
||||
)
|
||||
|
||||
func TestMembersAPIActionList(t *testing.T) {
|
||||
ep := url.URL{Scheme: "http", Host: "example.com"}
|
||||
act := &membersAPIActionList{}
|
||||
|
||||
wantURL := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: "example.com",
|
||||
Path: "/v2/members",
|
||||
}
|
||||
|
||||
got := *act.HTTPRequest(ep)
|
||||
err := assertRequest(got, http.MethodGet, wantURL, http.Header{}, nil)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMembersAPIActionAdd(t *testing.T) {
|
||||
ep := url.URL{Scheme: "http", Host: "example.com"}
|
||||
act := &membersAPIActionAdd{
|
||||
peerURLs: types.URLs([]url.URL{
|
||||
{Scheme: "https", Host: "127.0.0.1:8081"},
|
||||
{Scheme: "http", Host: "127.0.0.1:8080"},
|
||||
}),
|
||||
}
|
||||
|
||||
wantURL := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: "example.com",
|
||||
Path: "/v2/members",
|
||||
}
|
||||
wantHeader := http.Header{
|
||||
"Content-Type": []string{"application/json"},
|
||||
}
|
||||
wantBody := []byte(`{"peerURLs":["https://127.0.0.1:8081","http://127.0.0.1:8080"]}`)
|
||||
|
||||
got := *act.HTTPRequest(ep)
|
||||
err := assertRequest(got, http.MethodPost, wantURL, wantHeader, wantBody)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMembersAPIActionUpdate(t *testing.T) {
|
||||
ep := url.URL{Scheme: "http", Host: "example.com"}
|
||||
act := &membersAPIActionUpdate{
|
||||
memberID: "0xabcd",
|
||||
peerURLs: types.URLs([]url.URL{
|
||||
{Scheme: "https", Host: "127.0.0.1:8081"},
|
||||
{Scheme: "http", Host: "127.0.0.1:8080"},
|
||||
}),
|
||||
}
|
||||
|
||||
wantURL := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: "example.com",
|
||||
Path: "/v2/members/0xabcd",
|
||||
}
|
||||
wantHeader := http.Header{
|
||||
"Content-Type": []string{"application/json"},
|
||||
}
|
||||
wantBody := []byte(`{"peerURLs":["https://127.0.0.1:8081","http://127.0.0.1:8080"]}`)
|
||||
|
||||
got := *act.HTTPRequest(ep)
|
||||
err := assertRequest(got, http.MethodPut, wantURL, wantHeader, wantBody)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMembersAPIActionRemove(t *testing.T) {
|
||||
ep := url.URL{Scheme: "http", Host: "example.com"}
|
||||
act := &membersAPIActionRemove{memberID: "XXX"}
|
||||
|
||||
wantURL := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: "example.com",
|
||||
Path: "/v2/members/XXX",
|
||||
}
|
||||
|
||||
got := *act.HTTPRequest(ep)
|
||||
err := assertRequest(got, http.MethodDelete, wantURL, http.Header{}, nil)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMembersAPIActionLeader(t *testing.T) {
|
||||
ep := url.URL{Scheme: "http", Host: "example.com"}
|
||||
act := &membersAPIActionLeader{}
|
||||
|
||||
wantURL := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: "example.com",
|
||||
Path: "/v2/members/leader",
|
||||
}
|
||||
|
||||
got := *act.HTTPRequest(ep)
|
||||
err := assertRequest(got, http.MethodGet, wantURL, http.Header{}, nil)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssertStatusCode(t *testing.T) {
|
||||
if err := assertStatusCode(404, 400); err == nil {
|
||||
t.Errorf("assertStatusCode failed to detect conflict in 400 vs 404")
|
||||
}
|
||||
|
||||
if err := assertStatusCode(404, 400, 404); err != nil {
|
||||
t.Errorf("assertStatusCode found conflict in (404,400) vs 400: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestV2MembersURL(t *testing.T) {
|
||||
got := v2MembersURL(url.URL{
|
||||
Scheme: "http",
|
||||
Host: "foo.example.com:4002",
|
||||
Path: "/pants",
|
||||
})
|
||||
want := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: "foo.example.com:4002",
|
||||
Path: "/pants/v2/members",
|
||||
}
|
||||
|
||||
require.Truef(t, reflect.DeepEqual(want, got), "v2MembersURL got %#v, want %#v", got, want)
|
||||
}
|
||||
|
||||
func TestMemberUnmarshal(t *testing.T) {
|
||||
tests := []struct {
|
||||
body []byte
|
||||
wantMember Member
|
||||
wantError bool
|
||||
}{
|
||||
// no URLs, just check ID & Name
|
||||
{
|
||||
body: []byte(`{"id": "c", "name": "dungarees"}`),
|
||||
wantMember: Member{ID: "c", Name: "dungarees", PeerURLs: nil, ClientURLs: nil},
|
||||
},
|
||||
|
||||
// both client and peer URLs
|
||||
{
|
||||
body: []byte(`{"peerURLs": ["http://127.0.0.1:2379"], "clientURLs": ["http://127.0.0.1:2379"]}`),
|
||||
wantMember: Member{
|
||||
PeerURLs: []string{
|
||||
"http://127.0.0.1:2379",
|
||||
},
|
||||
ClientURLs: []string{
|
||||
"http://127.0.0.1:2379",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// multiple peer URLs
|
||||
{
|
||||
body: []byte(`{"peerURLs": ["http://127.0.0.1:2379", "https://example.com"]}`),
|
||||
wantMember: Member{
|
||||
PeerURLs: []string{
|
||||
"http://127.0.0.1:2379",
|
||||
"https://example.com",
|
||||
},
|
||||
ClientURLs: nil,
|
||||
},
|
||||
},
|
||||
|
||||
// multiple client URLs
|
||||
{
|
||||
body: []byte(`{"clientURLs": ["http://127.0.0.1:2379", "https://example.com"]}`),
|
||||
wantMember: Member{
|
||||
PeerURLs: nil,
|
||||
ClientURLs: []string{
|
||||
"http://127.0.0.1:2379",
|
||||
"https://example.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// invalid JSON
|
||||
{
|
||||
body: []byte(`{"peerU`),
|
||||
wantError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
got := Member{}
|
||||
err := json.Unmarshal(tt.body, &got)
|
||||
if tt.wantError != (err != nil) {
|
||||
t.Errorf("#%d: want error %t, got %v", i, tt.wantError, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tt.wantMember, got) {
|
||||
t.Errorf("#%d: incorrect output: want=%#v, got=%#v", i, tt.wantMember, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemberCollectionUnmarshalFail(t *testing.T) {
|
||||
mc := &memberCollection{}
|
||||
if err := mc.UnmarshalJSON([]byte(`{`)); err == nil {
|
||||
t.Errorf("got nil error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemberCollectionUnmarshal(t *testing.T) {
|
||||
tests := []struct {
|
||||
body []byte
|
||||
want memberCollection
|
||||
}{
|
||||
{
|
||||
body: []byte(`{}`),
|
||||
want: memberCollection([]Member{}),
|
||||
},
|
||||
{
|
||||
body: []byte(`{"members":[]}`),
|
||||
want: memberCollection([]Member{}),
|
||||
},
|
||||
{
|
||||
body: []byte(`{"members":[{"id":"2745e2525fce8fe","peerURLs":["http://127.0.0.1:7003"],"name":"node3","clientURLs":["http://127.0.0.1:4003"]},{"id":"42134f434382925","peerURLs":["http://127.0.0.1:2380","http://127.0.0.1:7001"],"name":"node1","clientURLs":["http://127.0.0.1:2379","http://127.0.0.1:4001"]},{"id":"94088180e21eb87b","peerURLs":["http://127.0.0.1:7002"],"name":"node2","clientURLs":["http://127.0.0.1:4002"]}]}`),
|
||||
want: memberCollection(
|
||||
[]Member{
|
||||
{
|
||||
ID: "2745e2525fce8fe",
|
||||
Name: "node3",
|
||||
PeerURLs: []string{
|
||||
"http://127.0.0.1:7003",
|
||||
},
|
||||
ClientURLs: []string{
|
||||
"http://127.0.0.1:4003",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "42134f434382925",
|
||||
Name: "node1",
|
||||
PeerURLs: []string{
|
||||
"http://127.0.0.1:2380",
|
||||
"http://127.0.0.1:7001",
|
||||
},
|
||||
ClientURLs: []string{
|
||||
"http://127.0.0.1:2379",
|
||||
"http://127.0.0.1:4001",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "94088180e21eb87b",
|
||||
Name: "node2",
|
||||
PeerURLs: []string{
|
||||
"http://127.0.0.1:7002",
|
||||
},
|
||||
ClientURLs: []string{
|
||||
"http://127.0.0.1:4002",
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
var got memberCollection
|
||||
err := json.Unmarshal(tt.body, &got)
|
||||
if err != nil {
|
||||
t.Errorf("#%d: unexpected error: %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tt.want, got) {
|
||||
t.Errorf("#%d: incorrect output: want=%#v, got=%#v", i, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemberCreateRequestMarshal(t *testing.T) {
|
||||
req := memberCreateOrUpdateRequest{
|
||||
PeerURLs: types.URLs([]url.URL{
|
||||
{Scheme: "http", Host: "127.0.0.1:8081"},
|
||||
{Scheme: "https", Host: "127.0.0.1:8080"},
|
||||
}),
|
||||
}
|
||||
want := []byte(`{"peerURLs":["http://127.0.0.1:8081","https://127.0.0.1:8080"]}`)
|
||||
|
||||
got, err := json.Marshal(&req)
|
||||
require.NoErrorf(t, err, "Marshal returned unexpected err")
|
||||
|
||||
require.Truef(t, reflect.DeepEqual(want, got), "Failed to marshal memberCreateRequest: want=%s, got=%s", want, got)
|
||||
}
|
||||
|
||||
func TestHTTPMembersAPIAddSuccess(t *testing.T) {
|
||||
wantAction := &membersAPIActionAdd{
|
||||
peerURLs: types.URLs([]url.URL{
|
||||
{Scheme: "http", Host: "127.0.0.1:7002"},
|
||||
}),
|
||||
}
|
||||
|
||||
mAPI := &httpMembersAPI{
|
||||
client: &actionAssertingHTTPClient{
|
||||
t: t,
|
||||
act: wantAction,
|
||||
resp: http.Response{
|
||||
StatusCode: http.StatusCreated,
|
||||
},
|
||||
body: []byte(`{"id":"94088180e21eb87b","peerURLs":["http://127.0.0.1:7002"]}`),
|
||||
},
|
||||
}
|
||||
|
||||
wantResponseMember := &Member{
|
||||
ID: "94088180e21eb87b",
|
||||
PeerURLs: []string{"http://127.0.0.1:7002"},
|
||||
}
|
||||
|
||||
m, err := mAPI.Add(context.Background(), "http://127.0.0.1:7002")
|
||||
if err != nil {
|
||||
t.Errorf("got non-nil err: %#v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(wantResponseMember, m) {
|
||||
t.Errorf("incorrect Member: want=%#v got=%#v", wantResponseMember, m)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPMembersAPIAddError(t *testing.T) {
|
||||
okPeer := "http://example.com:2379"
|
||||
tests := []struct {
|
||||
peerURL string
|
||||
client httpClient
|
||||
|
||||
// if wantErr == nil, assert that the returned error is non-nil
|
||||
// if wantErr != nil, assert that the returned error matches
|
||||
wantErr error
|
||||
}{
|
||||
// malformed peer URL
|
||||
{
|
||||
peerURL: ":",
|
||||
},
|
||||
|
||||
// generic httpClient failure
|
||||
{
|
||||
peerURL: okPeer,
|
||||
client: &staticHTTPClient{err: errors.New("fail")},
|
||||
},
|
||||
|
||||
// unrecognized HTTP status code
|
||||
{
|
||||
peerURL: okPeer,
|
||||
client: &staticHTTPClient{
|
||||
resp: http.Response{StatusCode: http.StatusTeapot},
|
||||
},
|
||||
},
|
||||
|
||||
// unmarshal body into membersError on StatusConflict
|
||||
{
|
||||
peerURL: okPeer,
|
||||
client: &staticHTTPClient{
|
||||
resp: http.Response{
|
||||
StatusCode: http.StatusConflict,
|
||||
},
|
||||
body: []byte(`{"message":"fail!"}`),
|
||||
},
|
||||
wantErr: membersError{Message: "fail!"},
|
||||
},
|
||||
|
||||
// fail to unmarshal body on StatusConflict
|
||||
{
|
||||
peerURL: okPeer,
|
||||
client: &staticHTTPClient{
|
||||
resp: http.Response{
|
||||
StatusCode: http.StatusConflict,
|
||||
},
|
||||
body: []byte(`{"`),
|
||||
},
|
||||
},
|
||||
|
||||
// fail to unmarshal body on StatusCreated
|
||||
{
|
||||
peerURL: okPeer,
|
||||
client: &staticHTTPClient{
|
||||
resp: http.Response{
|
||||
StatusCode: http.StatusCreated,
|
||||
},
|
||||
body: []byte(`{"id":"XX`),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
mAPI := &httpMembersAPI{client: tt.client}
|
||||
m, err := mAPI.Add(context.Background(), tt.peerURL)
|
||||
if err == nil {
|
||||
t.Errorf("#%d: got nil err", i)
|
||||
}
|
||||
if tt.wantErr != nil && !reflect.DeepEqual(tt.wantErr, err) {
|
||||
t.Errorf("#%d: incorrect error: want=%#v got=%#v", i, tt.wantErr, err)
|
||||
}
|
||||
if m != nil {
|
||||
t.Errorf("#%d: got non-nil Member", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPMembersAPIRemoveSuccess(t *testing.T) {
|
||||
wantAction := &membersAPIActionRemove{
|
||||
memberID: "94088180e21eb87b",
|
||||
}
|
||||
|
||||
mAPI := &httpMembersAPI{
|
||||
client: &actionAssertingHTTPClient{
|
||||
t: t,
|
||||
act: wantAction,
|
||||
resp: http.Response{
|
||||
StatusCode: http.StatusNoContent,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := mAPI.Remove(context.Background(), "94088180e21eb87b"); err != nil {
|
||||
t.Errorf("got non-nil err: %#v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPMembersAPIRemoveFail(t *testing.T) {
|
||||
tests := []httpClient{
|
||||
// generic error
|
||||
&staticHTTPClient{
|
||||
err: errors.New("fail"),
|
||||
},
|
||||
|
||||
// unexpected HTTP status code
|
||||
&staticHTTPClient{
|
||||
resp: http.Response{
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
mAPI := &httpMembersAPI{client: tt}
|
||||
if err := mAPI.Remove(context.Background(), "94088180e21eb87b"); err == nil {
|
||||
t.Errorf("#%d: got nil err", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPMembersAPIListSuccess(t *testing.T) {
|
||||
wantAction := &membersAPIActionList{}
|
||||
mAPI := &httpMembersAPI{
|
||||
client: &actionAssertingHTTPClient{
|
||||
t: t,
|
||||
act: wantAction,
|
||||
resp: http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
},
|
||||
body: []byte(`{"members":[{"id":"94088180e21eb87b","name":"node2","peerURLs":["http://127.0.0.1:7002"],"clientURLs":["http://127.0.0.1:4002"]}]}`),
|
||||
},
|
||||
}
|
||||
|
||||
wantResponseMembers := []Member{
|
||||
{
|
||||
ID: "94088180e21eb87b",
|
||||
Name: "node2",
|
||||
PeerURLs: []string{"http://127.0.0.1:7002"},
|
||||
ClientURLs: []string{"http://127.0.0.1:4002"},
|
||||
},
|
||||
}
|
||||
|
||||
m, err := mAPI.List(context.Background())
|
||||
if err != nil {
|
||||
t.Errorf("got non-nil err: %#v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(wantResponseMembers, m) {
|
||||
t.Errorf("incorrect Members: want=%#v got=%#v", wantResponseMembers, m)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPMembersAPIListError(t *testing.T) {
|
||||
tests := []httpClient{
|
||||
// generic httpClient failure
|
||||
&staticHTTPClient{err: errors.New("fail")},
|
||||
|
||||
// unrecognized HTTP status code
|
||||
&staticHTTPClient{
|
||||
resp: http.Response{StatusCode: http.StatusTeapot},
|
||||
},
|
||||
|
||||
// fail to unmarshal body on StatusOK
|
||||
&staticHTTPClient{
|
||||
resp: http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
},
|
||||
body: []byte(`[{"id":"XX`),
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
mAPI := &httpMembersAPI{client: tt}
|
||||
ms, err := mAPI.List(context.Background())
|
||||
if err == nil {
|
||||
t.Errorf("#%d: got nil err", i)
|
||||
}
|
||||
if ms != nil {
|
||||
t.Errorf("#%d: got non-nil Member slice", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPMembersAPILeaderSuccess(t *testing.T) {
|
||||
wantAction := &membersAPIActionLeader{}
|
||||
mAPI := &httpMembersAPI{
|
||||
client: &actionAssertingHTTPClient{
|
||||
t: t,
|
||||
act: wantAction,
|
||||
resp: http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
},
|
||||
body: []byte(`{"id":"94088180e21eb87b","name":"node2","peerURLs":["http://127.0.0.1:7002"],"clientURLs":["http://127.0.0.1:4002"]}`),
|
||||
},
|
||||
}
|
||||
|
||||
wantResponseMember := &Member{
|
||||
ID: "94088180e21eb87b",
|
||||
Name: "node2",
|
||||
PeerURLs: []string{"http://127.0.0.1:7002"},
|
||||
ClientURLs: []string{"http://127.0.0.1:4002"},
|
||||
}
|
||||
|
||||
m, err := mAPI.Leader(context.Background())
|
||||
if err != nil {
|
||||
t.Errorf("err = %v, want %v", err, nil)
|
||||
}
|
||||
if !reflect.DeepEqual(wantResponseMember, m) {
|
||||
t.Errorf("incorrect member: member = %v, want %v", wantResponseMember, m)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPMembersAPILeaderError(t *testing.T) {
|
||||
tests := []httpClient{
|
||||
// generic httpClient failure
|
||||
&staticHTTPClient{err: errors.New("fail")},
|
||||
|
||||
// unrecognized HTTP status code
|
||||
&staticHTTPClient{
|
||||
resp: http.Response{StatusCode: http.StatusTeapot},
|
||||
},
|
||||
|
||||
// fail to unmarshal body on StatusOK
|
||||
&staticHTTPClient{
|
||||
resp: http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
},
|
||||
body: []byte(`[{"id":"XX`),
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
mAPI := &httpMembersAPI{client: tt}
|
||||
m, err := mAPI.Leader(context.Background())
|
||||
if err == nil {
|
||||
t.Errorf("#%d: err = nil, want not nil", i)
|
||||
}
|
||||
if m != nil {
|
||||
t.Errorf("member slice = %v, want nil", m)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var (
|
||||
roleNotFoundRegExp *regexp.Regexp
|
||||
userNotFoundRegExp *regexp.Regexp
|
||||
)
|
||||
|
||||
func init() {
|
||||
roleNotFoundRegExp = regexp.MustCompile("auth: Role .* does not exist.")
|
||||
userNotFoundRegExp = regexp.MustCompile("auth: User .* does not exist.")
|
||||
}
|
||||
|
||||
// IsKeyNotFound returns true if the error code is ErrorCodeKeyNotFound.
|
||||
func IsKeyNotFound(err error) bool {
|
||||
var cErr Error
|
||||
return errors.As(err, &cErr) && cErr.Code == ErrorCodeKeyNotFound
|
||||
}
|
||||
|
||||
// IsRoleNotFound returns true if the error means role not found of v2 API.
|
||||
func IsRoleNotFound(err error) bool {
|
||||
var ae authError
|
||||
return errors.As(err, &ae) && roleNotFoundRegExp.MatchString(ae.Message)
|
||||
}
|
||||
|
||||
// IsUserNotFound returns true if the error means user not found of v2 API.
|
||||
func IsUserNotFound(err error) bool {
|
||||
var ae authError
|
||||
return errors.As(err, &ae) && userNotFoundRegExp.MatchString(ae.Message)
|
||||
}
|
|
@ -187,7 +187,7 @@
|
|||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
Copyright 2020 The etcd Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -23,7 +23,7 @@ import (
|
|||
)
|
||||
|
||||
func TestLockAndUnlock(t *testing.T) {
|
||||
f, err := os.CreateTemp("", "lock")
|
||||
f, err := os.CreateTemp(t.TempDir(), "lock")
|
||||
require.NoError(t, err)
|
||||
f.Close()
|
||||
defer func() {
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
module go.etcd.io/etcd/client/pkg/v3
|
||||
|
||||
go 1.23
|
||||
go 1.24
|
||||
|
||||
toolchain go1.23.6
|
||||
toolchain go1.24.4
|
||||
|
||||
require (
|
||||
github.com/coreos/go-systemd/v22 v22.5.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/sys v0.29.0
|
||||
golang.org/x/sys v0.33.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
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.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
|
@ -12,11 +12,11 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
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.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
|
@ -25,8 +25,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
|||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
|
|
|
@ -19,9 +19,6 @@ import (
|
|||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.etcd.io/etcd/client/pkg/v3/verify"
|
||||
)
|
||||
|
||||
|
@ -31,15 +28,12 @@ func BeforeTest(tb testing.TB) {
|
|||
|
||||
revertVerifyFunc := verify.EnableAllVerifications()
|
||||
|
||||
path, err := os.Getwd()
|
||||
require.NoError(tb, err)
|
||||
tempDir := tb.TempDir()
|
||||
require.NoError(tb, os.Chdir(tempDir))
|
||||
tb.Chdir(tempDir)
|
||||
tb.Logf("Changing working directory to: %s", tempDir)
|
||||
|
||||
tb.Cleanup(func() {
|
||||
revertVerifyFunc()
|
||||
assert.NoError(tb, os.Chdir(path))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,6 @@ func NewURLs(strs []string) (URLs, error) {
|
|||
return nil, fmt.Errorf("URL must not contain a path: %s", in)
|
||||
}
|
||||
case "unix", "unixs":
|
||||
break
|
||||
default:
|
||||
return nil, fmt.Errorf("URL scheme must be http, https, unix, or unixs: %s", in)
|
||||
}
|
||||
|
|
|
@ -187,7 +187,7 @@
|
|||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
Copyright 2020 The etcd Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -43,6 +43,7 @@ import (
|
|||
var (
|
||||
ErrNoAvailableEndpoints = errors.New("etcdclient: no available endpoints")
|
||||
ErrOldCluster = errors.New("etcdclient: old cluster version")
|
||||
ErrMutuallyExclusiveCfg = errors.New("Username/Password and Token configurations are mutually exclusive")
|
||||
)
|
||||
|
||||
// Client provides and manages an etcd v3 client session.
|
||||
|
@ -69,7 +70,10 @@ type Client struct {
|
|||
// Username is a user name for authentication.
|
||||
Username string
|
||||
// Password is a password for authentication.
|
||||
Password string
|
||||
Password string
|
||||
// Token is a JWT used for authentication instead of a password.
|
||||
Token string
|
||||
|
||||
authTokenBundle credentials.PerRPCCredentialsBundle
|
||||
|
||||
callOpts []grpc.CallOption
|
||||
|
@ -288,6 +292,11 @@ func (c *Client) Dial(ep string) (*grpc.ClientConn, error) {
|
|||
func (c *Client) getToken(ctx context.Context) error {
|
||||
var err error // return last error in a case of fail
|
||||
|
||||
if c.Token != "" {
|
||||
c.authTokenBundle.UpdateAuthToken(c.Token)
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.Username == "" || c.Password == "" {
|
||||
return nil
|
||||
}
|
||||
|
@ -376,6 +385,10 @@ func newClient(cfg *Config) (*Client, error) {
|
|||
creds = credentials.NewTransportCredential(cfg.TLS)
|
||||
}
|
||||
|
||||
if cfg.Token != "" && (cfg.Username != "" || cfg.Password != "") {
|
||||
return nil, ErrMutuallyExclusiveCfg
|
||||
}
|
||||
|
||||
// use a temporary skeleton client to bootstrap first connection
|
||||
baseCtx := context.TODO()
|
||||
if cfg.Context != nil {
|
||||
|
@ -414,6 +427,12 @@ func newClient(cfg *Config) (*Client, error) {
|
|||
client.Password = cfg.Password
|
||||
client.authTokenBundle = credentials.NewPerRPCCredentialBundle()
|
||||
}
|
||||
|
||||
if cfg.Token != "" {
|
||||
client.Token = cfg.Token
|
||||
client.authTokenBundle = credentials.NewPerRPCCredentialBundle()
|
||||
}
|
||||
|
||||
if cfg.MaxCallSendMsgSize > 0 || cfg.MaxCallRecvMsgSize > 0 {
|
||||
if cfg.MaxCallRecvMsgSize > 0 && cfg.MaxCallSendMsgSize > cfg.MaxCallRecvMsgSize {
|
||||
return nil, fmt.Errorf("gRPC message recv limit (%d bytes) must be greater than send limit (%d bytes)", cfg.MaxCallRecvMsgSize, cfg.MaxCallSendMsgSize)
|
||||
|
|
|
@ -196,18 +196,18 @@ func TestBackoffJitterFraction(t *testing.T) {
|
|||
|
||||
func TestIsHaltErr(t *testing.T) {
|
||||
assert.Truef(t,
|
||||
isHaltErr(context.TODO(), errors.New("etcdserver: some etcdserver error")),
|
||||
isHaltErr(t.Context(), errors.New("etcdserver: some etcdserver error")),
|
||||
"error created by errors.New should be unavailable error",
|
||||
)
|
||||
assert.Falsef(t,
|
||||
isHaltErr(context.TODO(), rpctypes.ErrGRPCStopped),
|
||||
isHaltErr(t.Context(), rpctypes.ErrGRPCStopped),
|
||||
`error "%v" should not be halt error`, rpctypes.ErrGRPCStopped,
|
||||
)
|
||||
assert.Falsef(t,
|
||||
isHaltErr(context.TODO(), rpctypes.ErrGRPCNoLeader),
|
||||
isHaltErr(t.Context(), rpctypes.ErrGRPCNoLeader),
|
||||
`error "%v" should not be halt error`, rpctypes.ErrGRPCNoLeader,
|
||||
)
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
assert.Falsef(t,
|
||||
isHaltErr(ctx, nil),
|
||||
"no error and active context should be halt error",
|
||||
|
@ -221,18 +221,18 @@ func TestIsHaltErr(t *testing.T) {
|
|||
|
||||
func TestIsUnavailableErr(t *testing.T) {
|
||||
assert.Falsef(t,
|
||||
isUnavailableErr(context.TODO(), errors.New("etcdserver: some etcdserver error")),
|
||||
isUnavailableErr(t.Context(), errors.New("etcdserver: some etcdserver error")),
|
||||
"error created by errors.New should not be unavailable error",
|
||||
)
|
||||
assert.Truef(t,
|
||||
isUnavailableErr(context.TODO(), rpctypes.ErrGRPCStopped),
|
||||
isUnavailableErr(t.Context(), rpctypes.ErrGRPCStopped),
|
||||
`error "%v" should be unavailable error`, rpctypes.ErrGRPCStopped,
|
||||
)
|
||||
assert.Falsef(t,
|
||||
isUnavailableErr(context.TODO(), rpctypes.ErrGRPCNotCapable),
|
||||
isUnavailableErr(t.Context(), rpctypes.ErrGRPCNotCapable),
|
||||
"error %v should not be unavailable error", rpctypes.ErrGRPCNotCapable,
|
||||
)
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
assert.Falsef(t,
|
||||
isUnavailableErr(ctx, nil),
|
||||
"no error and active context should not be unavailable error",
|
||||
|
@ -245,7 +245,7 @@ func TestIsUnavailableErr(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCloseCtxClient(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ctx := t.Context()
|
||||
c := NewCtxClient(ctx)
|
||||
err := c.Close()
|
||||
// Close returns ctx.toErr, a nil error means an open Done channel
|
||||
|
@ -255,7 +255,7 @@ func TestCloseCtxClient(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestWithLogger(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ctx := t.Context()
|
||||
c := NewCtxClient(ctx)
|
||||
if c.lg == nil {
|
||||
t.Errorf("unexpected nil in *zap.Logger")
|
||||
|
@ -268,7 +268,7 @@ func TestWithLogger(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestZapWithLogger(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ctx := t.Context()
|
||||
lg := zap.NewNop()
|
||||
c := NewCtxClient(ctx, WithZapLogger(lg))
|
||||
|
||||
|
@ -317,6 +317,75 @@ func TestAuthTokenBundleNoOverwrite(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNewWithOnlyJWT(t *testing.T) {
|
||||
// This call in particular changes working directory to the tmp dir of
|
||||
// the test. The `etcd-auth-test:1` can be created in local directory,
|
||||
// not exceeding the longest allowed path on OsX.
|
||||
testutil.BeforeTest(t)
|
||||
|
||||
// Create a mock AuthServer to handle Authenticate RPCs.
|
||||
lis, err := net.Listen("unix", "etcd-auth-test:1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer lis.Close()
|
||||
addr := "unix://" + lis.Addr().String()
|
||||
srv := grpc.NewServer()
|
||||
// Having a token removes the need to ever call Authenticate on the
|
||||
// server. If that happens then this will cause a connection failure.
|
||||
etcdserverpb.RegisterAuthServer(srv, mockFailingAuthServer{})
|
||||
go srv.Serve(lis)
|
||||
defer srv.Stop()
|
||||
|
||||
c, err := NewClient(t, Config{
|
||||
DialTimeout: 5 * time.Second,
|
||||
Endpoints: []string{addr},
|
||||
Token: "foo",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
meta, err := c.authTokenBundle.PerRPCCredentials().GetRequestMetadata(t.Context(), "")
|
||||
if err != nil {
|
||||
t.Errorf("Error building request metadata: %s", err)
|
||||
}
|
||||
|
||||
if tok, ok := meta[rpctypes.TokenFieldNameGRPC]; !ok {
|
||||
t.Error("Token was not successfuly set in the auth bundle")
|
||||
} else if tok != "foo" {
|
||||
t.Errorf("Incorrect token set in auth bundle, got '%s', expected 'foo'", tok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewOnlyJWTExclusivity(t *testing.T) {
|
||||
testutil.BeforeTest(t)
|
||||
|
||||
// Create a mock AuthServer to handle Authenticate RPCs.
|
||||
lis, err := net.Listen("unix", "etcd-auth-test:1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer lis.Close()
|
||||
addr := "unix://" + lis.Addr().String()
|
||||
srv := grpc.NewServer()
|
||||
// Having a token removes the need to ever call Authenticate on the
|
||||
// server. If that happens then this will cause a connection failure.
|
||||
etcdserverpb.RegisterAuthServer(srv, mockFailingAuthServer{})
|
||||
go srv.Serve(lis)
|
||||
defer srv.Stop()
|
||||
|
||||
_, err = NewClient(t, Config{
|
||||
DialTimeout: 5 * time.Second,
|
||||
Endpoints: []string{addr},
|
||||
Token: "foo",
|
||||
Username: "user",
|
||||
Password: "pass",
|
||||
})
|
||||
require.ErrorIs(t, ErrMutuallyExclusiveCfg, err)
|
||||
}
|
||||
|
||||
func TestSyncFiltersMembers(t *testing.T) {
|
||||
c, _ := NewClient(t, Config{Endpoints: []string{"http://254.0.0.1:12345"}})
|
||||
defer c.Close()
|
||||
|
@ -327,7 +396,7 @@ func TestSyncFiltersMembers(t *testing.T) {
|
|||
{ID: 2, Name: "isStartedAndNotLearner", ClientURLs: []string{"http://254.0.0.3:12345"}, IsLearner: false},
|
||||
},
|
||||
}
|
||||
c.Sync(context.Background())
|
||||
c.Sync(t.Context())
|
||||
|
||||
endpoints := c.Endpoints()
|
||||
if len(endpoints) != 1 || endpoints[0] != "http://254.0.0.3:12345" {
|
||||
|
@ -421,7 +490,7 @@ func TestClientRejectOldCluster(t *testing.T) {
|
|||
endpointToVersion[tt.endpoints[j]] = tt.versions[j]
|
||||
}
|
||||
c := &Client{
|
||||
ctx: context.Background(),
|
||||
ctx: t.Context(),
|
||||
endpoints: tt.endpoints,
|
||||
epMu: new(sync.RWMutex),
|
||||
Maintenance: &mockMaintenance{
|
||||
|
@ -476,6 +545,14 @@ func (mm mockMaintenance) Downgrade(ctx context.Context, action DowngradeAction,
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
type mockFailingAuthServer struct {
|
||||
*etcdserverpb.UnimplementedAuthServer
|
||||
}
|
||||
|
||||
func (mockFailingAuthServer) Authenticate(context.Context, *etcdserverpb.AuthenticateRequest) (*etcdserverpb.AuthenticateResponse, error) {
|
||||
return nil, errors.New("this auth server always fails")
|
||||
}
|
||||
|
||||
type mockAuthServer struct {
|
||||
*etcdserverpb.UnimplementedAuthServer
|
||||
}
|
||||
|
|
|
@ -66,6 +66,9 @@ type Config struct {
|
|||
// Password is a password for authentication.
|
||||
Password string `json:"password"`
|
||||
|
||||
// Token is a JWT used for authentication instead of a password.
|
||||
Token string `json:"token"`
|
||||
|
||||
// RejectOldCluster when set will refuse to create a client against an outdated cluster.
|
||||
RejectOldCluster bool `json:"reject-old-cluster"`
|
||||
|
||||
|
@ -130,6 +133,7 @@ type SecureConfig struct {
|
|||
type AuthConfig struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
func (cs *ConfigSpec) Clone() *ConfigSpec {
|
||||
|
@ -157,7 +161,7 @@ func (cs *ConfigSpec) Clone() *ConfigSpec {
|
|||
}
|
||||
|
||||
func (cfg AuthConfig) Empty() bool {
|
||||
return cfg.Username == "" && cfg.Password == ""
|
||||
return cfg.Username == "" && cfg.Password == "" && cfg.Token == ""
|
||||
}
|
||||
|
||||
// NewClientConfig creates a Config based on the provided ConfigSpec.
|
||||
|
@ -180,6 +184,7 @@ func NewClientConfig(confSpec *ConfigSpec, lg *zap.Logger) (*Config, error) {
|
|||
if confSpec.Auth != nil {
|
||||
cfg.Username = confSpec.Auth.Username
|
||||
cfg.Password = confSpec.Auth.Password
|
||||
cfg.Token = confSpec.Auth.Token
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
|
|
|
@ -71,6 +71,25 @@ func TestNewClientConfig(t *testing.T) {
|
|||
Password: "changeme",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "JWT specified",
|
||||
spec: ConfigSpec{
|
||||
Endpoints: []string{"http://192.168.0.12:2379"},
|
||||
DialTimeout: 1 * time.Second,
|
||||
KeepAliveTime: 4 * time.Second,
|
||||
KeepAliveTimeout: 6 * time.Second,
|
||||
Auth: &AuthConfig{
|
||||
Token: "test",
|
||||
},
|
||||
},
|
||||
expectedConf: Config{
|
||||
Endpoints: []string{"http://192.168.0.12:2379"},
|
||||
DialTimeout: 1 * time.Second,
|
||||
DialKeepAliveTime: 4 * time.Second,
|
||||
DialKeepAliveTimeout: 6 * time.Second,
|
||||
Token: "test",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "default secure transport",
|
||||
spec: ConfigSpec{
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
package credentials
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -25,7 +24,7 @@ import (
|
|||
|
||||
func TestUpdateAuthToken(t *testing.T) {
|
||||
bundle := NewPerRPCCredentialBundle()
|
||||
ctx := context.TODO()
|
||||
ctx := t.Context()
|
||||
|
||||
metadataBeforeUpdate, _ := bundle.PerRPCCredentials().GetRequestMetadata(ctx)
|
||||
assert.Empty(t, metadataBeforeUpdate)
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
package clientv3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
|
@ -27,7 +26,7 @@ import (
|
|||
)
|
||||
|
||||
func TestMetadataWithRequireLeader(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
ctx := t.Context()
|
||||
_, ok := metadata.FromOutgoingContext(ctx)
|
||||
require.Falsef(t, ok, "expected no outgoing metadata ctx key")
|
||||
|
||||
|
@ -48,7 +47,7 @@ func TestMetadataWithRequireLeader(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMetadataWithClientAPIVersion(t *testing.T) {
|
||||
ctx := withVersion(WithRequireLeader(context.TODO()))
|
||||
ctx := withVersion(WithRequireLeader(t.Context()))
|
||||
|
||||
md, ok := metadata.FromOutgoingContext(ctx)
|
||||
require.Truef(t, ok, "expected outgoing metadata ctx key")
|
||||
|
|
|
@ -1,46 +1,47 @@
|
|||
module go.etcd.io/etcd/client/v3
|
||||
|
||||
go 1.23
|
||||
go 1.24
|
||||
|
||||
toolchain go1.23.6
|
||||
toolchain go1.24.4
|
||||
|
||||
require (
|
||||
github.com/coreos/go-semver v0.3.1
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1
|
||||
github.com/prometheus/client_golang v1.20.5
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0
|
||||
github.com/prometheus/client_golang v1.22.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
go.etcd.io/etcd/api/v3 v3.6.0-alpha.0
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0
|
||||
go.uber.org/zap v1.27.0
|
||||
google.golang.org/grpc v1.70.0
|
||||
sigs.k8s.io/yaml v1.4.0
|
||||
google.golang.org/grpc v1.73.0
|
||||
sigs.k8s.io/yaml v1.5.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.62.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.65.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
google.golang.org/protobuf v1.36.4 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
golang.org/x/net v0.41.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
|
|
101
client/v3/go.sum
101
client/v3/go.sum
|
@ -6,12 +6,12 @@ github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr
|
|||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
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.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
|
@ -19,21 +19,20 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
|||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 h1:VD1gqscl4nYs1YxVuSdemTrSgTKrwOWDK0FVFMqm+Cg=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0/go.mod h1:4EgsQoS4TOhJizV+JTFg40qx1Ofh3XmXEQNBpgvNT40=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
|
||||
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/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
|
@ -42,40 +41,44 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
|||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
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/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
|
||||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
||||
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
||||
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.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
||||
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
||||
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
||||
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
|
||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE=
|
||||
go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
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=
|
||||
|
@ -85,20 +88,20 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
|
@ -107,18 +110,18 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
|||
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-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
|
||||
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
|
||||
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ=
|
||||
sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=
|
||||
|
|
|
@ -60,16 +60,28 @@ func (r *EtcdManualResolver) SetEndpoints(endpoints []string) {
|
|||
}
|
||||
|
||||
func (r EtcdManualResolver) updateState() {
|
||||
if r.CC != nil {
|
||||
addresses := make([]resolver.Address, len(r.endpoints))
|
||||
if getCC(r) != nil {
|
||||
eps := make([]resolver.Endpoint, len(r.endpoints))
|
||||
for i, ep := range r.endpoints {
|
||||
addr, serverName := endpoint.Interpret(ep)
|
||||
addresses[i] = resolver.Address{Addr: addr, ServerName: serverName}
|
||||
eps[i] = resolver.Endpoint{Addresses: []resolver.Address{
|
||||
{Addr: addr, ServerName: serverName},
|
||||
}}
|
||||
}
|
||||
state := resolver.State{
|
||||
Addresses: addresses,
|
||||
Endpoints: eps,
|
||||
ServiceConfig: r.serviceConfig,
|
||||
}
|
||||
r.UpdateState(state)
|
||||
}
|
||||
}
|
||||
|
||||
func getCC(r EtcdManualResolver) (cc resolver.ClientConn) {
|
||||
defer func() {
|
||||
if rec := recover(); rec != nil {
|
||||
cc = nil
|
||||
}
|
||||
}()
|
||||
|
||||
return r.CC()
|
||||
}
|
||||
|
|
|
@ -134,7 +134,7 @@ func (kv *kv) Compact(ctx context.Context, rev int64, opts ...CompactOption) (*C
|
|||
if err != nil {
|
||||
return nil, ContextError(ctx, err)
|
||||
}
|
||||
return (*CompactResponse)(resp), err
|
||||
return (*CompactResponse)(resp), nil
|
||||
}
|
||||
|
||||
func (kv *kv) Txn(ctx context.Context) Txn {
|
||||
|
|
|
@ -32,6 +32,8 @@ type Endpoint struct {
|
|||
// Metadata is the information associated with Addr, which may be used
|
||||
// to make load balancing decision.
|
||||
// Since etcd 3.1
|
||||
//
|
||||
// Deprecated: The field is deprecated and will be removed in 3.7.
|
||||
Metadata any
|
||||
}
|
||||
|
||||
|
|
|
@ -100,22 +100,26 @@ func (r *resolver) watch() {
|
|||
}
|
||||
}
|
||||
|
||||
addrs := convertToGRPCAddress(allUps)
|
||||
r.cc.UpdateState(gresolver.State{Addresses: addrs})
|
||||
eps := convertToGRPCEndpoint(allUps)
|
||||
r.cc.UpdateState(gresolver.State{Endpoints: eps})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func convertToGRPCAddress(ups map[string]*endpoints.Update) []gresolver.Address {
|
||||
var addrs []gresolver.Address
|
||||
func convertToGRPCEndpoint(ups map[string]*endpoints.Update) []gresolver.Endpoint {
|
||||
var eps []gresolver.Endpoint
|
||||
for _, up := range ups {
|
||||
addr := gresolver.Address{
|
||||
Addr: up.Endpoint.Addr,
|
||||
Metadata: up.Endpoint.Metadata,
|
||||
ep := gresolver.Endpoint{
|
||||
Addresses: []gresolver.Address{
|
||||
{
|
||||
Addr: up.Endpoint.Addr,
|
||||
Metadata: up.Endpoint.Metadata,
|
||||
},
|
||||
},
|
||||
}
|
||||
addrs = append(addrs, addr)
|
||||
eps = append(eps, ep)
|
||||
}
|
||||
return addrs
|
||||
return eps
|
||||
}
|
||||
|
||||
// ResolveNow is a no-op here.
|
||||
|
|
|
@ -76,7 +76,7 @@ func TestKvOrdering(t *testing.T) {
|
|||
tt.prevRev,
|
||||
sync.RWMutex{},
|
||||
}
|
||||
res, err := kv.Get(context.TODO(), "mockKey")
|
||||
res, err := kv.Get(t.Context(), "mockKey")
|
||||
if err != nil {
|
||||
t.Errorf("#%d: expected response %+v, got error %+v", i, tt.response, err)
|
||||
}
|
||||
|
@ -131,9 +131,9 @@ func TestTxnOrdering(t *testing.T) {
|
|||
sync.RWMutex{},
|
||||
}
|
||||
txn := &txnOrdering{
|
||||
kv.Txn(context.Background()),
|
||||
kv.Txn(t.Context()),
|
||||
kv,
|
||||
context.Background(),
|
||||
t.Context(),
|
||||
sync.Mutex{},
|
||||
[]clientv3.Cmp{},
|
||||
[]clientv3.Op{},
|
||||
|
|
|
@ -351,11 +351,11 @@ func isContextError(err error) bool {
|
|||
func contextErrToGRPCErr(err error) error {
|
||||
switch {
|
||||
case errors.Is(err, context.DeadlineExceeded):
|
||||
return status.Errorf(codes.DeadlineExceeded, err.Error())
|
||||
return status.Error(codes.DeadlineExceeded, err.Error())
|
||||
case errors.Is(err, context.Canceled):
|
||||
return status.Errorf(codes.Canceled, err.Error())
|
||||
return status.Error(codes.Canceled, err.Error())
|
||||
default:
|
||||
return status.Errorf(codes.Unknown, err.Error())
|
||||
return status.Error(codes.Unknown, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
package clientv3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -44,7 +43,7 @@ func TestTxnPanics(t *testing.T) {
|
|||
{
|
||||
f: func(errc chan string) {
|
||||
defer df(errc)
|
||||
kv.Txn(context.TODO()).If(cmp).If(cmp)
|
||||
kv.Txn(t.Context()).If(cmp).If(cmp)
|
||||
},
|
||||
|
||||
err: "cannot call If twice!",
|
||||
|
@ -52,7 +51,7 @@ func TestTxnPanics(t *testing.T) {
|
|||
{
|
||||
f: func(errc chan string) {
|
||||
defer df(errc)
|
||||
kv.Txn(context.TODO()).Then(op).If(cmp)
|
||||
kv.Txn(t.Context()).Then(op).If(cmp)
|
||||
},
|
||||
|
||||
err: "cannot call If after Then!",
|
||||
|
@ -60,7 +59,7 @@ func TestTxnPanics(t *testing.T) {
|
|||
{
|
||||
f: func(errc chan string) {
|
||||
defer df(errc)
|
||||
kv.Txn(context.TODO()).Else(op).If(cmp)
|
||||
kv.Txn(t.Context()).Else(op).If(cmp)
|
||||
},
|
||||
|
||||
err: "cannot call If after Else!",
|
||||
|
@ -68,7 +67,7 @@ func TestTxnPanics(t *testing.T) {
|
|||
{
|
||||
f: func(errc chan string) {
|
||||
defer df(errc)
|
||||
kv.Txn(context.TODO()).Then(op).Then(op)
|
||||
kv.Txn(t.Context()).Then(op).Then(op)
|
||||
},
|
||||
|
||||
err: "cannot call Then twice!",
|
||||
|
@ -76,7 +75,7 @@ func TestTxnPanics(t *testing.T) {
|
|||
{
|
||||
f: func(errc chan string) {
|
||||
defer df(errc)
|
||||
kv.Txn(context.TODO()).Else(op).Then(op)
|
||||
kv.Txn(t.Context()).Else(op).Then(op)
|
||||
},
|
||||
|
||||
err: "cannot call Then after Else!",
|
||||
|
@ -84,7 +83,7 @@ func TestTxnPanics(t *testing.T) {
|
|||
{
|
||||
f: func(errc chan string) {
|
||||
defer df(errc)
|
||||
kv.Txn(context.TODO()).Else(op).Else(op)
|
||||
kv.Txn(t.Context()).Else(op).Else(op)
|
||||
},
|
||||
|
||||
err: "cannot call Else twice!",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue