##// END OF EJS Templates
automation: create Python 3.5 variant of requirements.txt...
Gregory Szorc -
r47966:546e812a default
parent child Browse files
Show More
@@ -0,0 +1,194
1 #
2 # This file is autogenerated by pip-compile
3 # To update, run:
4 #
5 # pip-compile --generate-hashes --output-file=contrib/automation/linux-requirements-py3.5.txt contrib/automation/linux-requirements.txt.in
6 #
7 astroid==2.4.2 \
8 --hash=sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703 \
9 --hash=sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386
10 # via pylint
11 docutils==0.17.1 \
12 --hash=sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125 \
13 --hash=sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61
14 # via -r contrib/automation/linux-requirements.txt.in
15 fuzzywuzzy==0.18.0 \
16 --hash=sha256:45016e92264780e58972dca1b3d939ac864b78437422beecebb3095f8efd00e8 \
17 --hash=sha256:928244b28db720d1e0ee7587acf660ea49d7e4c632569cad4f1cd7e68a5f0993
18 # via -r contrib/automation/linux-requirements.txt.in
19 idna==3.1 \
20 --hash=sha256:5205d03e7bcbb919cc9c19885f9920d622ca52448306f2377daede5cf3faac16 \
21 --hash=sha256:c5b02147e01ea9920e6b0a3f1f7bb833612d507592c837a6c49552768f4054e1
22 # via yarl
23 isort==4.3.21 \
24 --hash=sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1 \
25 --hash=sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd
26 # via
27 # -r contrib/automation/linux-requirements.txt.in
28 # pylint
29 lazy-object-proxy==1.4.3 \
30 --hash=sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d \
31 --hash=sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449 \
32 --hash=sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08 \
33 --hash=sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a \
34 --hash=sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50 \
35 --hash=sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd \
36 --hash=sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239 \
37 --hash=sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb \
38 --hash=sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea \
39 --hash=sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e \
40 --hash=sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156 \
41 --hash=sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142 \
42 --hash=sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442 \
43 --hash=sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62 \
44 --hash=sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db \
45 --hash=sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531 \
46 --hash=sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383 \
47 --hash=sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a \
48 --hash=sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357 \
49 --hash=sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4 \
50 --hash=sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0
51 # via astroid
52 mccabe==0.6.1 \
53 --hash=sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42 \
54 --hash=sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f
55 # via pylint
56 multidict==5.0.2 \
57 --hash=sha256:060d68ae3e674c913ec41a464916f12c4d7ff17a3a9ebbf37ba7f2c681c2b33e \
58 --hash=sha256:06f39f0ddc308dab4e5fa282d145f90cd38d7ed75390fc83335636909a9ec191 \
59 --hash=sha256:17847fede1aafdb7e74e01bb34ab47a1a1ea726e8184c623c45d7e428d2d5d34 \
60 --hash=sha256:1cd102057b09223b919f9447c669cf2efabeefb42a42ae6233f25ffd7ee31a79 \
61 --hash=sha256:20cc9b2dd31761990abff7d0e63cd14dbfca4ebb52a77afc917b603473951a38 \
62 --hash=sha256:2576e30bbec004e863d87216bc34abe24962cc2e964613241a1c01c7681092ab \
63 --hash=sha256:2ab9cad4c5ef5c41e1123ed1f89f555aabefb9391d4e01fd6182de970b7267ed \
64 --hash=sha256:359ea00e1b53ceef282232308da9d9a3f60d645868a97f64df19485c7f9ef628 \
65 --hash=sha256:3e61cc244fd30bd9fdfae13bdd0c5ec65da51a86575ff1191255cae677045ffe \
66 --hash=sha256:43c7a87d8c31913311a1ab24b138254a0ee89142983b327a2c2eab7a7d10fea9 \
67 --hash=sha256:4a3f19da871befa53b48dd81ee48542f519beffa13090dc135fffc18d8fe36db \
68 --hash=sha256:4df708ef412fd9b59b7e6c77857e64c1f6b4c0116b751cb399384ec9a28baa66 \
69 --hash=sha256:59182e975b8c197d0146a003d0f0d5dc5487ce4899502061d8df585b0f51fba2 \
70 --hash=sha256:6128d2c0956fd60e39ec7d1c8f79426f0c915d36458df59ddd1f0cff0340305f \
71 --hash=sha256:6168839491a533fa75f3f5d48acbb829475e6c7d9fa5c6e245153b5f79b986a3 \
72 --hash=sha256:62abab8088704121297d39c8f47156cb8fab1da731f513e59ba73946b22cf3d0 \
73 --hash=sha256:653b2bbb0bbf282c37279dd04f429947ac92713049e1efc615f68d4e64b1dbc2 \
74 --hash=sha256:6566749cd78cb37cbf8e8171b5cd2cbfc03c99f0891de12255cf17a11c07b1a3 \
75 --hash=sha256:76cbdb22f48de64811f9ce1dd4dee09665f84f32d6a26de249a50c1e90e244e0 \
76 --hash=sha256:8efcf070d60fd497db771429b1c769a3783e3a0dd96c78c027e676990176adc5 \
77 --hash=sha256:8fa4549f341a057feec4c3139056ba73e17ed03a506469f447797a51f85081b5 \
78 --hash=sha256:9380b3f2b00b23a4106ba9dd022df3e6e2e84e1788acdbdd27603b621b3288df \
79 --hash=sha256:9ed9b280f7778ad6f71826b38a73c2fdca4077817c64bc1102fdada58e75c03c \
80 --hash=sha256:a7b8b5bd16376c8ac2977748bd978a200326af5145d8d0e7f799e2b355d425b6 \
81 --hash=sha256:af271c2540d1cd2a137bef8d95a8052230aa1cda26dd3b2c73d858d89993d518 \
82 --hash=sha256:b561e76c9e21402d9a446cdae13398f9942388b9bff529f32dfa46220af54d00 \
83 --hash=sha256:b82400ef848bbac6b9035a105ac6acaa1fb3eea0d164e35bbb21619b88e49fed \
84 --hash=sha256:b98af08d7bb37d3456a22f689819ea793e8d6961b9629322d7728c4039071641 \
85 --hash=sha256:c58e53e1c73109fdf4b759db9f2939325f510a8a5215135330fe6755921e4886 \
86 --hash=sha256:cbabfc12b401d074298bfda099c58dfa5348415ae2e4ec841290627cb7cb6b2e \
87 --hash=sha256:d4a6fb98e9e9be3f7d70fd3e852369c00a027bd5ed0f3e8ade3821bcad257408 \
88 --hash=sha256:d99da85d6890267292065e654a329e1d2f483a5d2485e347383800e616a8c0b1 \
89 --hash=sha256:e58db0e0d60029915f7fc95a8683fa815e204f2e1990f1fb46a7778d57ca8c35 \
90 --hash=sha256:e5bf89fe57f702a046c7ec718fe330ed50efd4bcf74722940db2eb0919cddb1c \
91 --hash=sha256:f612e8ef8408391a4a3366e3508bab8ef97b063b4918a317cb6e6de4415f01af \
92 --hash=sha256:f65a2442c113afde52fb09f9a6276bbc31da71add99dc76c3adf6083234e07c6 \
93 --hash=sha256:fa0503947a99a1be94f799fac89d67a5e20c333e78ddae16e8534b151cdc588a
94 # via yarl
95 pyflakes==2.3.1 \
96 --hash=sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3 \
97 --hash=sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db
98 # via -r contrib/automation/linux-requirements.txt.in
99 pygments==2.9.0 \
100 --hash=sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f \
101 --hash=sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e
102 # via -r contrib/automation/linux-requirements.txt.in
103 pylint==2.6.2 \
104 --hash=sha256:718b74786ea7ed07aa0c58bf572154d4679f960d26e9641cc1de204a30b87fc9 \
105 --hash=sha256:e71c2e9614a4f06e36498f310027942b0f4f2fde20aebb01655b31edc63b9eaf
106 # via -r contrib/automation/linux-requirements.txt.in
107 python-levenshtein==0.12.2 \
108 --hash=sha256:dc2395fbd148a1ab31090dd113c366695934b9e85fe5a4b2a032745efd0346f6
109 # via -r contrib/automation/linux-requirements.txt.in
110 pyyaml==5.3.1 \
111 --hash=sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97 \
112 --hash=sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76 \
113 --hash=sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2 \
114 --hash=sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e \
115 --hash=sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648 \
116 --hash=sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf \
117 --hash=sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f \
118 --hash=sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2 \
119 --hash=sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee \
120 --hash=sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a \
121 --hash=sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d \
122 --hash=sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c \
123 --hash=sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a
124 # via vcrpy
125 six==1.16.0 \
126 --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
127 --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
128 # via
129 # astroid
130 # vcrpy
131 toml==0.10.2 \
132 --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \
133 --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f
134 # via pylint
135 typed-ast==1.4.3 ; python_version >= "3.0" and platform_python_implementation != "PyPy" \
136 --hash=sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace \
137 --hash=sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff \
138 --hash=sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266 \
139 --hash=sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528 \
140 --hash=sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6 \
141 --hash=sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808 \
142 --hash=sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4 \
143 --hash=sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363 \
144 --hash=sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341 \
145 --hash=sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04 \
146 --hash=sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41 \
147 --hash=sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e \
148 --hash=sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3 \
149 --hash=sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899 \
150 --hash=sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805 \
151 --hash=sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c \
152 --hash=sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c \
153 --hash=sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39 \
154 --hash=sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a \
155 --hash=sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3 \
156 --hash=sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7 \
157 --hash=sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f \
158 --hash=sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075 \
159 --hash=sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0 \
160 --hash=sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40 \
161 --hash=sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428 \
162 --hash=sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927 \
163 --hash=sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3 \
164 --hash=sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f \
165 --hash=sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65
166 # via
167 # -r contrib/automation/linux-requirements.txt.in
168 # astroid
169 vcrpy==4.1.1 \
170 --hash=sha256:12c3fcdae7b88ecf11fc0d3e6d77586549d4575a2ceee18e82eee75c1f626162 \
171 --hash=sha256:57095bf22fc0a2d99ee9674cdafebed0f3ba763018582450706f7d3a74fff599
172 # via -r contrib/automation/linux-requirements.txt.in
173 wrapt==1.12.1 \
174 --hash=sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7
175 # via
176 # astroid
177 # vcrpy
178 yarl==1.3.0 \
179 --hash=sha256:024ecdc12bc02b321bc66b41327f930d1c2c543fa9a561b39861da9388ba7aa9 \
180 --hash=sha256:2f3010703295fbe1aec51023740871e64bb9664c789cba5a6bdf404e93f7568f \
181 --hash=sha256:3890ab952d508523ef4881457c4099056546593fa05e93da84c7250516e632eb \
182 --hash=sha256:3e2724eb9af5dc41648e5bb304fcf4891adc33258c6e14e2a7414ea32541e320 \
183 --hash=sha256:5badb97dd0abf26623a9982cd448ff12cb39b8e4c94032ccdedf22ce01a64842 \
184 --hash=sha256:73f447d11b530d860ca1e6b582f947688286ad16ca42256413083d13f260b7a0 \
185 --hash=sha256:7ab825726f2940c16d92aaec7d204cfc34ac26c0040da727cf8ba87255a33829 \
186 --hash=sha256:b25de84a8c20540531526dfbb0e2d2b648c13fd5dd126728c496d7c3fea33310 \
187 --hash=sha256:c6e341f5a6562af74ba55205dbd56d248daf1b5748ec48a0200ba227bb9e33f4 \
188 --hash=sha256:c9bb7c249c4432cd47e75af3864bc02d26c9594f49c82e2a28624417f0ae63b8 \
189 --hash=sha256:e060906c0c585565c718d1c3841747b61c5439af2211e185f6739a9412dfbde1
190 # via vcrpy
191
192 # WARNING: The following packages were not pinned, but pip requires them to be
193 # pinned when the requirements file includes hashes. Consider using the --allow-unsafe flag.
194 # setuptools
@@ -1,1325 +1,1335
1 # aws.py - Automation code for Amazon Web Services
1 # aws.py - Automation code for Amazon Web Services
2 #
2 #
3 # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 # no-check-code because Python 3 native.
8 # no-check-code because Python 3 native.
9
9
10 import contextlib
10 import contextlib
11 import copy
11 import copy
12 import hashlib
12 import hashlib
13 import json
13 import json
14 import os
14 import os
15 import pathlib
15 import pathlib
16 import subprocess
16 import subprocess
17 import time
17 import time
18
18
19 import boto3
19 import boto3
20 import botocore.exceptions
20 import botocore.exceptions
21
21
22 from .linux import BOOTSTRAP_DEBIAN
22 from .linux import BOOTSTRAP_DEBIAN
23 from .ssh import (
23 from .ssh import (
24 exec_command as ssh_exec_command,
24 exec_command as ssh_exec_command,
25 wait_for_ssh,
25 wait_for_ssh,
26 )
26 )
27 from .winrm import (
27 from .winrm import (
28 run_powershell,
28 run_powershell,
29 wait_for_winrm,
29 wait_for_winrm,
30 )
30 )
31
31
32
32
33 SOURCE_ROOT = pathlib.Path(
33 SOURCE_ROOT = pathlib.Path(
34 os.path.abspath(__file__)
34 os.path.abspath(__file__)
35 ).parent.parent.parent.parent
35 ).parent.parent.parent.parent
36
36
37 INSTALL_WINDOWS_DEPENDENCIES = (
37 INSTALL_WINDOWS_DEPENDENCIES = (
38 SOURCE_ROOT / 'contrib' / 'install-windows-dependencies.ps1'
38 SOURCE_ROOT / 'contrib' / 'install-windows-dependencies.ps1'
39 )
39 )
40
40
41
41
42 INSTANCE_TYPES_WITH_STORAGE = {
42 INSTANCE_TYPES_WITH_STORAGE = {
43 'c5d',
43 'c5d',
44 'd2',
44 'd2',
45 'h1',
45 'h1',
46 'i3',
46 'i3',
47 'm5ad',
47 'm5ad',
48 'm5d',
48 'm5d',
49 'r5d',
49 'r5d',
50 'r5ad',
50 'r5ad',
51 'x1',
51 'x1',
52 'z1d',
52 'z1d',
53 }
53 }
54
54
55
55
56 AMAZON_ACCOUNT_ID = '801119661308'
56 AMAZON_ACCOUNT_ID = '801119661308'
57 DEBIAN_ACCOUNT_ID = '379101102735'
57 DEBIAN_ACCOUNT_ID = '379101102735'
58 DEBIAN_ACCOUNT_ID_2 = '136693071363'
58 DEBIAN_ACCOUNT_ID_2 = '136693071363'
59 UBUNTU_ACCOUNT_ID = '099720109477'
59 UBUNTU_ACCOUNT_ID = '099720109477'
60
60
61
61
62 WINDOWS_BASE_IMAGE_NAME = 'Windows_Server-2019-English-Full-Base-*'
62 WINDOWS_BASE_IMAGE_NAME = 'Windows_Server-2019-English-Full-Base-*'
63
63
64
64
65 KEY_PAIRS = {
65 KEY_PAIRS = {
66 'automation',
66 'automation',
67 }
67 }
68
68
69
69
70 SECURITY_GROUPS = {
70 SECURITY_GROUPS = {
71 'linux-dev-1': {
71 'linux-dev-1': {
72 'description': 'Mercurial Linux instances that perform build/test automation',
72 'description': 'Mercurial Linux instances that perform build/test automation',
73 'ingress': [
73 'ingress': [
74 {
74 {
75 'FromPort': 22,
75 'FromPort': 22,
76 'ToPort': 22,
76 'ToPort': 22,
77 'IpProtocol': 'tcp',
77 'IpProtocol': 'tcp',
78 'IpRanges': [
78 'IpRanges': [
79 {
79 {
80 'CidrIp': '0.0.0.0/0',
80 'CidrIp': '0.0.0.0/0',
81 'Description': 'SSH from entire Internet',
81 'Description': 'SSH from entire Internet',
82 },
82 },
83 ],
83 ],
84 },
84 },
85 ],
85 ],
86 },
86 },
87 'windows-dev-1': {
87 'windows-dev-1': {
88 'description': 'Mercurial Windows instances that perform build automation',
88 'description': 'Mercurial Windows instances that perform build automation',
89 'ingress': [
89 'ingress': [
90 {
90 {
91 'FromPort': 22,
91 'FromPort': 22,
92 'ToPort': 22,
92 'ToPort': 22,
93 'IpProtocol': 'tcp',
93 'IpProtocol': 'tcp',
94 'IpRanges': [
94 'IpRanges': [
95 {
95 {
96 'CidrIp': '0.0.0.0/0',
96 'CidrIp': '0.0.0.0/0',
97 'Description': 'SSH from entire Internet',
97 'Description': 'SSH from entire Internet',
98 },
98 },
99 ],
99 ],
100 },
100 },
101 {
101 {
102 'FromPort': 3389,
102 'FromPort': 3389,
103 'ToPort': 3389,
103 'ToPort': 3389,
104 'IpProtocol': 'tcp',
104 'IpProtocol': 'tcp',
105 'IpRanges': [
105 'IpRanges': [
106 {
106 {
107 'CidrIp': '0.0.0.0/0',
107 'CidrIp': '0.0.0.0/0',
108 'Description': 'RDP from entire Internet',
108 'Description': 'RDP from entire Internet',
109 },
109 },
110 ],
110 ],
111 },
111 },
112 {
112 {
113 'FromPort': 5985,
113 'FromPort': 5985,
114 'ToPort': 5986,
114 'ToPort': 5986,
115 'IpProtocol': 'tcp',
115 'IpProtocol': 'tcp',
116 'IpRanges': [
116 'IpRanges': [
117 {
117 {
118 'CidrIp': '0.0.0.0/0',
118 'CidrIp': '0.0.0.0/0',
119 'Description': 'PowerShell Remoting (Windows Remote Management)',
119 'Description': 'PowerShell Remoting (Windows Remote Management)',
120 },
120 },
121 ],
121 ],
122 },
122 },
123 ],
123 ],
124 },
124 },
125 }
125 }
126
126
127
127
128 IAM_ROLES = {
128 IAM_ROLES = {
129 'ephemeral-ec2-role-1': {
129 'ephemeral-ec2-role-1': {
130 'description': 'Mercurial temporary EC2 instances',
130 'description': 'Mercurial temporary EC2 instances',
131 'policy_arns': [
131 'policy_arns': [
132 'arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM',
132 'arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM',
133 ],
133 ],
134 },
134 },
135 }
135 }
136
136
137
137
138 ASSUME_ROLE_POLICY_DOCUMENT = '''
138 ASSUME_ROLE_POLICY_DOCUMENT = '''
139 {
139 {
140 "Version": "2012-10-17",
140 "Version": "2012-10-17",
141 "Statement": [
141 "Statement": [
142 {
142 {
143 "Effect": "Allow",
143 "Effect": "Allow",
144 "Principal": {
144 "Principal": {
145 "Service": "ec2.amazonaws.com"
145 "Service": "ec2.amazonaws.com"
146 },
146 },
147 "Action": "sts:AssumeRole"
147 "Action": "sts:AssumeRole"
148 }
148 }
149 ]
149 ]
150 }
150 }
151 '''.strip()
151 '''.strip()
152
152
153
153
154 IAM_INSTANCE_PROFILES = {
154 IAM_INSTANCE_PROFILES = {
155 'ephemeral-ec2-1': {
155 'ephemeral-ec2-1': {
156 'roles': [
156 'roles': [
157 'ephemeral-ec2-role-1',
157 'ephemeral-ec2-role-1',
158 ],
158 ],
159 }
159 }
160 }
160 }
161
161
162
162
163 # User Data for Windows EC2 instance. Mainly used to set the password
163 # User Data for Windows EC2 instance. Mainly used to set the password
164 # and configure WinRM.
164 # and configure WinRM.
165 # Inspired by the User Data script used by Packer
165 # Inspired by the User Data script used by Packer
166 # (from https://www.packer.io/intro/getting-started/build-image.html).
166 # (from https://www.packer.io/intro/getting-started/build-image.html).
167 WINDOWS_USER_DATA = r'''
167 WINDOWS_USER_DATA = r'''
168 <powershell>
168 <powershell>
169
169
170 # TODO enable this once we figure out what is failing.
170 # TODO enable this once we figure out what is failing.
171 #$ErrorActionPreference = "stop"
171 #$ErrorActionPreference = "stop"
172
172
173 # Set administrator password
173 # Set administrator password
174 net user Administrator "%s"
174 net user Administrator "%s"
175 wmic useraccount where "name='Administrator'" set PasswordExpires=FALSE
175 wmic useraccount where "name='Administrator'" set PasswordExpires=FALSE
176
176
177 # First, make sure WinRM can't be connected to
177 # First, make sure WinRM can't be connected to
178 netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new enable=yes action=block
178 netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new enable=yes action=block
179
179
180 # Delete any existing WinRM listeners
180 # Delete any existing WinRM listeners
181 winrm delete winrm/config/listener?Address=*+Transport=HTTP 2>$Null
181 winrm delete winrm/config/listener?Address=*+Transport=HTTP 2>$Null
182 winrm delete winrm/config/listener?Address=*+Transport=HTTPS 2>$Null
182 winrm delete winrm/config/listener?Address=*+Transport=HTTPS 2>$Null
183
183
184 # Create a new WinRM listener and configure
184 # Create a new WinRM listener and configure
185 winrm create winrm/config/listener?Address=*+Transport=HTTP
185 winrm create winrm/config/listener?Address=*+Transport=HTTP
186 winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="0"}'
186 winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="0"}'
187 winrm set winrm/config '@{MaxTimeoutms="7200000"}'
187 winrm set winrm/config '@{MaxTimeoutms="7200000"}'
188 winrm set winrm/config/service '@{AllowUnencrypted="true"}'
188 winrm set winrm/config/service '@{AllowUnencrypted="true"}'
189 winrm set winrm/config/service '@{MaxConcurrentOperationsPerUser="12000"}'
189 winrm set winrm/config/service '@{MaxConcurrentOperationsPerUser="12000"}'
190 winrm set winrm/config/service/auth '@{Basic="true"}'
190 winrm set winrm/config/service/auth '@{Basic="true"}'
191 winrm set winrm/config/client/auth '@{Basic="true"}'
191 winrm set winrm/config/client/auth '@{Basic="true"}'
192
192
193 # Configure UAC to allow privilege elevation in remote shells
193 # Configure UAC to allow privilege elevation in remote shells
194 $Key = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System'
194 $Key = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System'
195 $Setting = 'LocalAccountTokenFilterPolicy'
195 $Setting = 'LocalAccountTokenFilterPolicy'
196 Set-ItemProperty -Path $Key -Name $Setting -Value 1 -Force
196 Set-ItemProperty -Path $Key -Name $Setting -Value 1 -Force
197
197
198 # Avoid long usernames in the temp directory path because the '~' causes extra quoting in ssh output
198 # Avoid long usernames in the temp directory path because the '~' causes extra quoting in ssh output
199 [System.Environment]::SetEnvironmentVariable('TMP', 'C:\Temp', [System.EnvironmentVariableTarget]::User)
199 [System.Environment]::SetEnvironmentVariable('TMP', 'C:\Temp', [System.EnvironmentVariableTarget]::User)
200 [System.Environment]::SetEnvironmentVariable('TEMP', 'C:\Temp', [System.EnvironmentVariableTarget]::User)
200 [System.Environment]::SetEnvironmentVariable('TEMP', 'C:\Temp', [System.EnvironmentVariableTarget]::User)
201
201
202 # Configure and restart the WinRM Service; Enable the required firewall exception
202 # Configure and restart the WinRM Service; Enable the required firewall exception
203 Stop-Service -Name WinRM
203 Stop-Service -Name WinRM
204 Set-Service -Name WinRM -StartupType Automatic
204 Set-Service -Name WinRM -StartupType Automatic
205 netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new action=allow localip=any remoteip=any
205 netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new action=allow localip=any remoteip=any
206 Start-Service -Name WinRM
206 Start-Service -Name WinRM
207
207
208 # Disable firewall on private network interfaces so prompts don't appear.
208 # Disable firewall on private network interfaces so prompts don't appear.
209 Set-NetFirewallProfile -Name private -Enabled false
209 Set-NetFirewallProfile -Name private -Enabled false
210 </powershell>
210 </powershell>
211 '''.lstrip()
211 '''.lstrip()
212
212
213
213
214 WINDOWS_BOOTSTRAP_POWERSHELL = '''
214 WINDOWS_BOOTSTRAP_POWERSHELL = '''
215 Write-Output "installing PowerShell dependencies"
215 Write-Output "installing PowerShell dependencies"
216 Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
216 Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
217 Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
217 Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
218 Install-Module -Name OpenSSHUtils -RequiredVersion 0.0.2.0
218 Install-Module -Name OpenSSHUtils -RequiredVersion 0.0.2.0
219
219
220 Write-Output "installing OpenSSL server"
220 Write-Output "installing OpenSSL server"
221 Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
221 Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
222 # Various tools will attempt to use older versions of .NET. So we enable
222 # Various tools will attempt to use older versions of .NET. So we enable
223 # the feature that provides them so it doesn't have to be auto-enabled
223 # the feature that provides them so it doesn't have to be auto-enabled
224 # later.
224 # later.
225 Write-Output "enabling .NET Framework feature"
225 Write-Output "enabling .NET Framework feature"
226 Install-WindowsFeature -Name Net-Framework-Core
226 Install-WindowsFeature -Name Net-Framework-Core
227 '''
227 '''
228
228
229
229
230 class AWSConnection:
230 class AWSConnection:
231 """Manages the state of a connection with AWS."""
231 """Manages the state of a connection with AWS."""
232
232
233 def __init__(self, automation, region: str, ensure_ec2_state: bool = True):
233 def __init__(self, automation, region: str, ensure_ec2_state: bool = True):
234 self.automation = automation
234 self.automation = automation
235 self.local_state_path = automation.state_path
235 self.local_state_path = automation.state_path
236
236
237 self.prefix = 'hg-'
237 self.prefix = 'hg-'
238
238
239 self.session = boto3.session.Session(region_name=region)
239 self.session = boto3.session.Session(region_name=region)
240 self.ec2client = self.session.client('ec2')
240 self.ec2client = self.session.client('ec2')
241 self.ec2resource = self.session.resource('ec2')
241 self.ec2resource = self.session.resource('ec2')
242 self.iamclient = self.session.client('iam')
242 self.iamclient = self.session.client('iam')
243 self.iamresource = self.session.resource('iam')
243 self.iamresource = self.session.resource('iam')
244 self.security_groups = {}
244 self.security_groups = {}
245
245
246 if ensure_ec2_state:
246 if ensure_ec2_state:
247 ensure_key_pairs(automation.state_path, self.ec2resource)
247 ensure_key_pairs(automation.state_path, self.ec2resource)
248 self.security_groups = ensure_security_groups(self.ec2resource)
248 self.security_groups = ensure_security_groups(self.ec2resource)
249 ensure_iam_state(self.iamclient, self.iamresource)
249 ensure_iam_state(self.iamclient, self.iamresource)
250
250
251 def key_pair_path_private(self, name):
251 def key_pair_path_private(self, name):
252 """Path to a key pair private key file."""
252 """Path to a key pair private key file."""
253 return self.local_state_path / 'keys' / ('keypair-%s' % name)
253 return self.local_state_path / 'keys' / ('keypair-%s' % name)
254
254
255 def key_pair_path_public(self, name):
255 def key_pair_path_public(self, name):
256 return self.local_state_path / 'keys' / ('keypair-%s.pub' % name)
256 return self.local_state_path / 'keys' / ('keypair-%s.pub' % name)
257
257
258
258
259 def rsa_key_fingerprint(p: pathlib.Path):
259 def rsa_key_fingerprint(p: pathlib.Path):
260 """Compute the fingerprint of an RSA private key."""
260 """Compute the fingerprint of an RSA private key."""
261
261
262 # TODO use rsa package.
262 # TODO use rsa package.
263 res = subprocess.run(
263 res = subprocess.run(
264 [
264 [
265 'openssl',
265 'openssl',
266 'pkcs8',
266 'pkcs8',
267 '-in',
267 '-in',
268 str(p),
268 str(p),
269 '-nocrypt',
269 '-nocrypt',
270 '-topk8',
270 '-topk8',
271 '-outform',
271 '-outform',
272 'DER',
272 'DER',
273 ],
273 ],
274 capture_output=True,
274 capture_output=True,
275 check=True,
275 check=True,
276 )
276 )
277
277
278 sha1 = hashlib.sha1(res.stdout).hexdigest()
278 sha1 = hashlib.sha1(res.stdout).hexdigest()
279 return ':'.join(a + b for a, b in zip(sha1[::2], sha1[1::2]))
279 return ':'.join(a + b for a, b in zip(sha1[::2], sha1[1::2]))
280
280
281
281
282 def ensure_key_pairs(state_path: pathlib.Path, ec2resource, prefix='hg-'):
282 def ensure_key_pairs(state_path: pathlib.Path, ec2resource, prefix='hg-'):
283 remote_existing = {}
283 remote_existing = {}
284
284
285 for kpi in ec2resource.key_pairs.all():
285 for kpi in ec2resource.key_pairs.all():
286 if kpi.name.startswith(prefix):
286 if kpi.name.startswith(prefix):
287 remote_existing[kpi.name[len(prefix) :]] = kpi.key_fingerprint
287 remote_existing[kpi.name[len(prefix) :]] = kpi.key_fingerprint
288
288
289 # Validate that we have these keys locally.
289 # Validate that we have these keys locally.
290 key_path = state_path / 'keys'
290 key_path = state_path / 'keys'
291 key_path.mkdir(exist_ok=True, mode=0o700)
291 key_path.mkdir(exist_ok=True, mode=0o700)
292
292
293 def remove_remote(name):
293 def remove_remote(name):
294 print('deleting key pair %s' % name)
294 print('deleting key pair %s' % name)
295 key = ec2resource.KeyPair(name)
295 key = ec2resource.KeyPair(name)
296 key.delete()
296 key.delete()
297
297
298 def remove_local(name):
298 def remove_local(name):
299 pub_full = key_path / ('keypair-%s.pub' % name)
299 pub_full = key_path / ('keypair-%s.pub' % name)
300 priv_full = key_path / ('keypair-%s' % name)
300 priv_full = key_path / ('keypair-%s' % name)
301
301
302 print('removing %s' % pub_full)
302 print('removing %s' % pub_full)
303 pub_full.unlink()
303 pub_full.unlink()
304 print('removing %s' % priv_full)
304 print('removing %s' % priv_full)
305 priv_full.unlink()
305 priv_full.unlink()
306
306
307 local_existing = {}
307 local_existing = {}
308
308
309 for f in sorted(os.listdir(key_path)):
309 for f in sorted(os.listdir(key_path)):
310 if not f.startswith('keypair-') or not f.endswith('.pub'):
310 if not f.startswith('keypair-') or not f.endswith('.pub'):
311 continue
311 continue
312
312
313 name = f[len('keypair-') : -len('.pub')]
313 name = f[len('keypair-') : -len('.pub')]
314
314
315 pub_full = key_path / f
315 pub_full = key_path / f
316 priv_full = key_path / ('keypair-%s' % name)
316 priv_full = key_path / ('keypair-%s' % name)
317
317
318 with open(pub_full, 'r', encoding='ascii') as fh:
318 with open(pub_full, 'r', encoding='ascii') as fh:
319 data = fh.read()
319 data = fh.read()
320
320
321 if not data.startswith('ssh-rsa '):
321 if not data.startswith('ssh-rsa '):
322 print(
322 print(
323 'unexpected format for key pair file: %s; removing' % pub_full
323 'unexpected format for key pair file: %s; removing' % pub_full
324 )
324 )
325 pub_full.unlink()
325 pub_full.unlink()
326 priv_full.unlink()
326 priv_full.unlink()
327 continue
327 continue
328
328
329 local_existing[name] = rsa_key_fingerprint(priv_full)
329 local_existing[name] = rsa_key_fingerprint(priv_full)
330
330
331 for name in sorted(set(remote_existing) | set(local_existing)):
331 for name in sorted(set(remote_existing) | set(local_existing)):
332 if name not in local_existing:
332 if name not in local_existing:
333 actual = '%s%s' % (prefix, name)
333 actual = '%s%s' % (prefix, name)
334 print('remote key %s does not exist locally' % name)
334 print('remote key %s does not exist locally' % name)
335 remove_remote(actual)
335 remove_remote(actual)
336 del remote_existing[name]
336 del remote_existing[name]
337
337
338 elif name not in remote_existing:
338 elif name not in remote_existing:
339 print('local key %s does not exist remotely' % name)
339 print('local key %s does not exist remotely' % name)
340 remove_local(name)
340 remove_local(name)
341 del local_existing[name]
341 del local_existing[name]
342
342
343 elif remote_existing[name] != local_existing[name]:
343 elif remote_existing[name] != local_existing[name]:
344 print(
344 print(
345 'key fingerprint mismatch for %s; '
345 'key fingerprint mismatch for %s; '
346 'removing from local and remote' % name
346 'removing from local and remote' % name
347 )
347 )
348 remove_local(name)
348 remove_local(name)
349 remove_remote('%s%s' % (prefix, name))
349 remove_remote('%s%s' % (prefix, name))
350 del local_existing[name]
350 del local_existing[name]
351 del remote_existing[name]
351 del remote_existing[name]
352
352
353 missing = KEY_PAIRS - set(remote_existing)
353 missing = KEY_PAIRS - set(remote_existing)
354
354
355 for name in sorted(missing):
355 for name in sorted(missing):
356 actual = '%s%s' % (prefix, name)
356 actual = '%s%s' % (prefix, name)
357 print('creating key pair %s' % actual)
357 print('creating key pair %s' % actual)
358
358
359 priv_full = key_path / ('keypair-%s' % name)
359 priv_full = key_path / ('keypair-%s' % name)
360 pub_full = key_path / ('keypair-%s.pub' % name)
360 pub_full = key_path / ('keypair-%s.pub' % name)
361
361
362 kp = ec2resource.create_key_pair(KeyName=actual)
362 kp = ec2resource.create_key_pair(KeyName=actual)
363
363
364 with priv_full.open('w', encoding='ascii') as fh:
364 with priv_full.open('w', encoding='ascii') as fh:
365 fh.write(kp.key_material)
365 fh.write(kp.key_material)
366 fh.write('\n')
366 fh.write('\n')
367
367
368 priv_full.chmod(0o0600)
368 priv_full.chmod(0o0600)
369
369
370 # SSH public key can be extracted via `ssh-keygen`.
370 # SSH public key can be extracted via `ssh-keygen`.
371 with pub_full.open('w', encoding='ascii') as fh:
371 with pub_full.open('w', encoding='ascii') as fh:
372 subprocess.run(
372 subprocess.run(
373 ['ssh-keygen', '-y', '-f', str(priv_full)],
373 ['ssh-keygen', '-y', '-f', str(priv_full)],
374 stdout=fh,
374 stdout=fh,
375 check=True,
375 check=True,
376 )
376 )
377
377
378 pub_full.chmod(0o0600)
378 pub_full.chmod(0o0600)
379
379
380
380
381 def delete_instance_profile(profile):
381 def delete_instance_profile(profile):
382 for role in profile.roles:
382 for role in profile.roles:
383 print(
383 print(
384 'removing role %s from instance profile %s'
384 'removing role %s from instance profile %s'
385 % (role.name, profile.name)
385 % (role.name, profile.name)
386 )
386 )
387 profile.remove_role(RoleName=role.name)
387 profile.remove_role(RoleName=role.name)
388
388
389 print('deleting instance profile %s' % profile.name)
389 print('deleting instance profile %s' % profile.name)
390 profile.delete()
390 profile.delete()
391
391
392
392
393 def ensure_iam_state(iamclient, iamresource, prefix='hg-'):
393 def ensure_iam_state(iamclient, iamresource, prefix='hg-'):
394 """Ensure IAM state is in sync with our canonical definition."""
394 """Ensure IAM state is in sync with our canonical definition."""
395
395
396 remote_profiles = {}
396 remote_profiles = {}
397
397
398 for profile in iamresource.instance_profiles.all():
398 for profile in iamresource.instance_profiles.all():
399 if profile.name.startswith(prefix):
399 if profile.name.startswith(prefix):
400 remote_profiles[profile.name[len(prefix) :]] = profile
400 remote_profiles[profile.name[len(prefix) :]] = profile
401
401
402 for name in sorted(set(remote_profiles) - set(IAM_INSTANCE_PROFILES)):
402 for name in sorted(set(remote_profiles) - set(IAM_INSTANCE_PROFILES)):
403 delete_instance_profile(remote_profiles[name])
403 delete_instance_profile(remote_profiles[name])
404 del remote_profiles[name]
404 del remote_profiles[name]
405
405
406 remote_roles = {}
406 remote_roles = {}
407
407
408 for role in iamresource.roles.all():
408 for role in iamresource.roles.all():
409 if role.name.startswith(prefix):
409 if role.name.startswith(prefix):
410 remote_roles[role.name[len(prefix) :]] = role
410 remote_roles[role.name[len(prefix) :]] = role
411
411
412 for name in sorted(set(remote_roles) - set(IAM_ROLES)):
412 for name in sorted(set(remote_roles) - set(IAM_ROLES)):
413 role = remote_roles[name]
413 role = remote_roles[name]
414
414
415 print('removing role %s' % role.name)
415 print('removing role %s' % role.name)
416 role.delete()
416 role.delete()
417 del remote_roles[name]
417 del remote_roles[name]
418
418
419 # We've purged remote state that doesn't belong. Create missing
419 # We've purged remote state that doesn't belong. Create missing
420 # instance profiles and roles.
420 # instance profiles and roles.
421 for name in sorted(set(IAM_INSTANCE_PROFILES) - set(remote_profiles)):
421 for name in sorted(set(IAM_INSTANCE_PROFILES) - set(remote_profiles)):
422 actual = '%s%s' % (prefix, name)
422 actual = '%s%s' % (prefix, name)
423 print('creating IAM instance profile %s' % actual)
423 print('creating IAM instance profile %s' % actual)
424
424
425 profile = iamresource.create_instance_profile(
425 profile = iamresource.create_instance_profile(
426 InstanceProfileName=actual
426 InstanceProfileName=actual
427 )
427 )
428 remote_profiles[name] = profile
428 remote_profiles[name] = profile
429
429
430 waiter = iamclient.get_waiter('instance_profile_exists')
430 waiter = iamclient.get_waiter('instance_profile_exists')
431 waiter.wait(InstanceProfileName=actual)
431 waiter.wait(InstanceProfileName=actual)
432 print('IAM instance profile %s is available' % actual)
432 print('IAM instance profile %s is available' % actual)
433
433
434 for name in sorted(set(IAM_ROLES) - set(remote_roles)):
434 for name in sorted(set(IAM_ROLES) - set(remote_roles)):
435 entry = IAM_ROLES[name]
435 entry = IAM_ROLES[name]
436
436
437 actual = '%s%s' % (prefix, name)
437 actual = '%s%s' % (prefix, name)
438 print('creating IAM role %s' % actual)
438 print('creating IAM role %s' % actual)
439
439
440 role = iamresource.create_role(
440 role = iamresource.create_role(
441 RoleName=actual,
441 RoleName=actual,
442 Description=entry['description'],
442 Description=entry['description'],
443 AssumeRolePolicyDocument=ASSUME_ROLE_POLICY_DOCUMENT,
443 AssumeRolePolicyDocument=ASSUME_ROLE_POLICY_DOCUMENT,
444 )
444 )
445
445
446 waiter = iamclient.get_waiter('role_exists')
446 waiter = iamclient.get_waiter('role_exists')
447 waiter.wait(RoleName=actual)
447 waiter.wait(RoleName=actual)
448 print('IAM role %s is available' % actual)
448 print('IAM role %s is available' % actual)
449
449
450 remote_roles[name] = role
450 remote_roles[name] = role
451
451
452 for arn in entry['policy_arns']:
452 for arn in entry['policy_arns']:
453 print('attaching policy %s to %s' % (arn, role.name))
453 print('attaching policy %s to %s' % (arn, role.name))
454 role.attach_policy(PolicyArn=arn)
454 role.attach_policy(PolicyArn=arn)
455
455
456 # Now reconcile state of profiles.
456 # Now reconcile state of profiles.
457 for name, meta in sorted(IAM_INSTANCE_PROFILES.items()):
457 for name, meta in sorted(IAM_INSTANCE_PROFILES.items()):
458 profile = remote_profiles[name]
458 profile = remote_profiles[name]
459 wanted = {'%s%s' % (prefix, role) for role in meta['roles']}
459 wanted = {'%s%s' % (prefix, role) for role in meta['roles']}
460 have = {role.name for role in profile.roles}
460 have = {role.name for role in profile.roles}
461
461
462 for role in sorted(have - wanted):
462 for role in sorted(have - wanted):
463 print('removing role %s from %s' % (role, profile.name))
463 print('removing role %s from %s' % (role, profile.name))
464 profile.remove_role(RoleName=role)
464 profile.remove_role(RoleName=role)
465
465
466 for role in sorted(wanted - have):
466 for role in sorted(wanted - have):
467 print('adding role %s to %s' % (role, profile.name))
467 print('adding role %s to %s' % (role, profile.name))
468 profile.add_role(RoleName=role)
468 profile.add_role(RoleName=role)
469
469
470
470
471 def find_image(ec2resource, owner_id, name, reverse_sort_field=None):
471 def find_image(ec2resource, owner_id, name, reverse_sort_field=None):
472 """Find an AMI by its owner ID and name."""
472 """Find an AMI by its owner ID and name."""
473
473
474 images = ec2resource.images.filter(
474 images = ec2resource.images.filter(
475 Filters=[
475 Filters=[
476 {
476 {
477 'Name': 'owner-id',
477 'Name': 'owner-id',
478 'Values': [owner_id],
478 'Values': [owner_id],
479 },
479 },
480 {
480 {
481 'Name': 'state',
481 'Name': 'state',
482 'Values': ['available'],
482 'Values': ['available'],
483 },
483 },
484 {
484 {
485 'Name': 'image-type',
485 'Name': 'image-type',
486 'Values': ['machine'],
486 'Values': ['machine'],
487 },
487 },
488 {
488 {
489 'Name': 'name',
489 'Name': 'name',
490 'Values': [name],
490 'Values': [name],
491 },
491 },
492 ]
492 ]
493 )
493 )
494
494
495 if reverse_sort_field:
495 if reverse_sort_field:
496 images = sorted(
496 images = sorted(
497 images,
497 images,
498 key=lambda image: getattr(image, reverse_sort_field),
498 key=lambda image: getattr(image, reverse_sort_field),
499 reverse=True,
499 reverse=True,
500 )
500 )
501
501
502 for image in images:
502 for image in images:
503 return image
503 return image
504
504
505 raise Exception('unable to find image for %s' % name)
505 raise Exception('unable to find image for %s' % name)
506
506
507
507
508 def ensure_security_groups(ec2resource, prefix='hg-'):
508 def ensure_security_groups(ec2resource, prefix='hg-'):
509 """Ensure all necessary Mercurial security groups are present.
509 """Ensure all necessary Mercurial security groups are present.
510
510
511 All security groups are prefixed with ``hg-`` by default. Any security
511 All security groups are prefixed with ``hg-`` by default. Any security
512 groups having this prefix but aren't in our list are deleted.
512 groups having this prefix but aren't in our list are deleted.
513 """
513 """
514 existing = {}
514 existing = {}
515
515
516 for group in ec2resource.security_groups.all():
516 for group in ec2resource.security_groups.all():
517 if group.group_name.startswith(prefix):
517 if group.group_name.startswith(prefix):
518 existing[group.group_name[len(prefix) :]] = group
518 existing[group.group_name[len(prefix) :]] = group
519
519
520 purge = set(existing) - set(SECURITY_GROUPS)
520 purge = set(existing) - set(SECURITY_GROUPS)
521
521
522 for name in sorted(purge):
522 for name in sorted(purge):
523 group = existing[name]
523 group = existing[name]
524 print('removing legacy security group: %s' % group.group_name)
524 print('removing legacy security group: %s' % group.group_name)
525 group.delete()
525 group.delete()
526
526
527 security_groups = {}
527 security_groups = {}
528
528
529 for name, group in sorted(SECURITY_GROUPS.items()):
529 for name, group in sorted(SECURITY_GROUPS.items()):
530 if name in existing:
530 if name in existing:
531 security_groups[name] = existing[name]
531 security_groups[name] = existing[name]
532 continue
532 continue
533
533
534 actual = '%s%s' % (prefix, name)
534 actual = '%s%s' % (prefix, name)
535 print('adding security group %s' % actual)
535 print('adding security group %s' % actual)
536
536
537 group_res = ec2resource.create_security_group(
537 group_res = ec2resource.create_security_group(
538 Description=group['description'],
538 Description=group['description'],
539 GroupName=actual,
539 GroupName=actual,
540 )
540 )
541
541
542 group_res.authorize_ingress(
542 group_res.authorize_ingress(
543 IpPermissions=group['ingress'],
543 IpPermissions=group['ingress'],
544 )
544 )
545
545
546 security_groups[name] = group_res
546 security_groups[name] = group_res
547
547
548 return security_groups
548 return security_groups
549
549
550
550
551 def terminate_ec2_instances(ec2resource, prefix='hg-'):
551 def terminate_ec2_instances(ec2resource, prefix='hg-'):
552 """Terminate all EC2 instances managed by us."""
552 """Terminate all EC2 instances managed by us."""
553 waiting = []
553 waiting = []
554
554
555 for instance in ec2resource.instances.all():
555 for instance in ec2resource.instances.all():
556 if instance.state['Name'] == 'terminated':
556 if instance.state['Name'] == 'terminated':
557 continue
557 continue
558
558
559 for tag in instance.tags or []:
559 for tag in instance.tags or []:
560 if tag['Key'] == 'Name' and tag['Value'].startswith(prefix):
560 if tag['Key'] == 'Name' and tag['Value'].startswith(prefix):
561 print('terminating %s' % instance.id)
561 print('terminating %s' % instance.id)
562 instance.terminate()
562 instance.terminate()
563 waiting.append(instance)
563 waiting.append(instance)
564
564
565 for instance in waiting:
565 for instance in waiting:
566 instance.wait_until_terminated()
566 instance.wait_until_terminated()
567
567
568
568
569 def remove_resources(c, prefix='hg-'):
569 def remove_resources(c, prefix='hg-'):
570 """Purge all of our resources in this EC2 region."""
570 """Purge all of our resources in this EC2 region."""
571 ec2resource = c.ec2resource
571 ec2resource = c.ec2resource
572 iamresource = c.iamresource
572 iamresource = c.iamresource
573
573
574 terminate_ec2_instances(ec2resource, prefix=prefix)
574 terminate_ec2_instances(ec2resource, prefix=prefix)
575
575
576 for image in ec2resource.images.filter(Owners=['self']):
576 for image in ec2resource.images.filter(Owners=['self']):
577 if image.name.startswith(prefix):
577 if image.name.startswith(prefix):
578 remove_ami(ec2resource, image)
578 remove_ami(ec2resource, image)
579
579
580 for group in ec2resource.security_groups.all():
580 for group in ec2resource.security_groups.all():
581 if group.group_name.startswith(prefix):
581 if group.group_name.startswith(prefix):
582 print('removing security group %s' % group.group_name)
582 print('removing security group %s' % group.group_name)
583 group.delete()
583 group.delete()
584
584
585 for profile in iamresource.instance_profiles.all():
585 for profile in iamresource.instance_profiles.all():
586 if profile.name.startswith(prefix):
586 if profile.name.startswith(prefix):
587 delete_instance_profile(profile)
587 delete_instance_profile(profile)
588
588
589 for role in iamresource.roles.all():
589 for role in iamresource.roles.all():
590 if role.name.startswith(prefix):
590 if role.name.startswith(prefix):
591 for p in role.attached_policies.all():
591 for p in role.attached_policies.all():
592 print('detaching policy %s from %s' % (p.arn, role.name))
592 print('detaching policy %s from %s' % (p.arn, role.name))
593 role.detach_policy(PolicyArn=p.arn)
593 role.detach_policy(PolicyArn=p.arn)
594
594
595 print('removing role %s' % role.name)
595 print('removing role %s' % role.name)
596 role.delete()
596 role.delete()
597
597
598
598
599 def wait_for_ip_addresses(instances):
599 def wait_for_ip_addresses(instances):
600 """Wait for the public IP addresses of an iterable of instances."""
600 """Wait for the public IP addresses of an iterable of instances."""
601 for instance in instances:
601 for instance in instances:
602 while True:
602 while True:
603 if not instance.public_ip_address:
603 if not instance.public_ip_address:
604 time.sleep(2)
604 time.sleep(2)
605 instance.reload()
605 instance.reload()
606 continue
606 continue
607
607
608 print(
608 print(
609 'public IP address for %s: %s'
609 'public IP address for %s: %s'
610 % (instance.id, instance.public_ip_address)
610 % (instance.id, instance.public_ip_address)
611 )
611 )
612 break
612 break
613
613
614
614
615 def remove_ami(ec2resource, image):
615 def remove_ami(ec2resource, image):
616 """Remove an AMI and its underlying snapshots."""
616 """Remove an AMI and its underlying snapshots."""
617 snapshots = []
617 snapshots = []
618
618
619 for device in image.block_device_mappings:
619 for device in image.block_device_mappings:
620 if 'Ebs' in device:
620 if 'Ebs' in device:
621 snapshots.append(ec2resource.Snapshot(device['Ebs']['SnapshotId']))
621 snapshots.append(ec2resource.Snapshot(device['Ebs']['SnapshotId']))
622
622
623 print('deregistering %s' % image.id)
623 print('deregistering %s' % image.id)
624 image.deregister()
624 image.deregister()
625
625
626 for snapshot in snapshots:
626 for snapshot in snapshots:
627 print('deleting snapshot %s' % snapshot.id)
627 print('deleting snapshot %s' % snapshot.id)
628 snapshot.delete()
628 snapshot.delete()
629
629
630
630
631 def wait_for_ssm(ssmclient, instances):
631 def wait_for_ssm(ssmclient, instances):
632 """Wait for SSM to come online for an iterable of instance IDs."""
632 """Wait for SSM to come online for an iterable of instance IDs."""
633 while True:
633 while True:
634 res = ssmclient.describe_instance_information(
634 res = ssmclient.describe_instance_information(
635 Filters=[
635 Filters=[
636 {
636 {
637 'Key': 'InstanceIds',
637 'Key': 'InstanceIds',
638 'Values': [i.id for i in instances],
638 'Values': [i.id for i in instances],
639 },
639 },
640 ],
640 ],
641 )
641 )
642
642
643 available = len(res['InstanceInformationList'])
643 available = len(res['InstanceInformationList'])
644 wanted = len(instances)
644 wanted = len(instances)
645
645
646 print('%d/%d instances available in SSM' % (available, wanted))
646 print('%d/%d instances available in SSM' % (available, wanted))
647
647
648 if available == wanted:
648 if available == wanted:
649 return
649 return
650
650
651 time.sleep(2)
651 time.sleep(2)
652
652
653
653
654 def run_ssm_command(ssmclient, instances, document_name, parameters):
654 def run_ssm_command(ssmclient, instances, document_name, parameters):
655 """Run a PowerShell script on an EC2 instance."""
655 """Run a PowerShell script on an EC2 instance."""
656
656
657 res = ssmclient.send_command(
657 res = ssmclient.send_command(
658 InstanceIds=[i.id for i in instances],
658 InstanceIds=[i.id for i in instances],
659 DocumentName=document_name,
659 DocumentName=document_name,
660 Parameters=parameters,
660 Parameters=parameters,
661 CloudWatchOutputConfig={
661 CloudWatchOutputConfig={
662 'CloudWatchOutputEnabled': True,
662 'CloudWatchOutputEnabled': True,
663 },
663 },
664 )
664 )
665
665
666 command_id = res['Command']['CommandId']
666 command_id = res['Command']['CommandId']
667
667
668 for instance in instances:
668 for instance in instances:
669 while True:
669 while True:
670 try:
670 try:
671 res = ssmclient.get_command_invocation(
671 res = ssmclient.get_command_invocation(
672 CommandId=command_id,
672 CommandId=command_id,
673 InstanceId=instance.id,
673 InstanceId=instance.id,
674 )
674 )
675 except botocore.exceptions.ClientError as e:
675 except botocore.exceptions.ClientError as e:
676 if e.response['Error']['Code'] == 'InvocationDoesNotExist':
676 if e.response['Error']['Code'] == 'InvocationDoesNotExist':
677 print('could not find SSM command invocation; waiting')
677 print('could not find SSM command invocation; waiting')
678 time.sleep(1)
678 time.sleep(1)
679 continue
679 continue
680 else:
680 else:
681 raise
681 raise
682
682
683 if res['Status'] == 'Success':
683 if res['Status'] == 'Success':
684 break
684 break
685 elif res['Status'] in ('Pending', 'InProgress', 'Delayed'):
685 elif res['Status'] in ('Pending', 'InProgress', 'Delayed'):
686 time.sleep(2)
686 time.sleep(2)
687 else:
687 else:
688 raise Exception(
688 raise Exception(
689 'command failed on %s: %s' % (instance.id, res['Status'])
689 'command failed on %s: %s' % (instance.id, res['Status'])
690 )
690 )
691
691
692
692
693 @contextlib.contextmanager
693 @contextlib.contextmanager
694 def temporary_ec2_instances(ec2resource, config):
694 def temporary_ec2_instances(ec2resource, config):
695 """Create temporary EC2 instances.
695 """Create temporary EC2 instances.
696
696
697 This is a proxy to ``ec2client.run_instances(**config)`` that takes care of
697 This is a proxy to ``ec2client.run_instances(**config)`` that takes care of
698 managing the lifecycle of the instances.
698 managing the lifecycle of the instances.
699
699
700 When the context manager exits, the instances are terminated.
700 When the context manager exits, the instances are terminated.
701
701
702 The context manager evaluates to the list of data structures
702 The context manager evaluates to the list of data structures
703 describing each created instance. The instances may not be available
703 describing each created instance. The instances may not be available
704 for work immediately: it is up to the caller to wait for the instance
704 for work immediately: it is up to the caller to wait for the instance
705 to start responding.
705 to start responding.
706 """
706 """
707
707
708 ids = None
708 ids = None
709
709
710 try:
710 try:
711 res = ec2resource.create_instances(**config)
711 res = ec2resource.create_instances(**config)
712
712
713 ids = [i.id for i in res]
713 ids = [i.id for i in res]
714 print('started instances: %s' % ' '.join(ids))
714 print('started instances: %s' % ' '.join(ids))
715
715
716 yield res
716 yield res
717 finally:
717 finally:
718 if ids:
718 if ids:
719 print('terminating instances: %s' % ' '.join(ids))
719 print('terminating instances: %s' % ' '.join(ids))
720 for instance in res:
720 for instance in res:
721 instance.terminate()
721 instance.terminate()
722 print('terminated %d instances' % len(ids))
722 print('terminated %d instances' % len(ids))
723
723
724
724
725 @contextlib.contextmanager
725 @contextlib.contextmanager
726 def create_temp_windows_ec2_instances(
726 def create_temp_windows_ec2_instances(
727 c: AWSConnection, config, bootstrap: bool = False
727 c: AWSConnection, config, bootstrap: bool = False
728 ):
728 ):
729 """Create temporary Windows EC2 instances.
729 """Create temporary Windows EC2 instances.
730
730
731 This is a higher-level wrapper around ``create_temp_ec2_instances()`` that
731 This is a higher-level wrapper around ``create_temp_ec2_instances()`` that
732 configures the Windows instance for Windows Remote Management. The emitted
732 configures the Windows instance for Windows Remote Management. The emitted
733 instances will have a ``winrm_client`` attribute containing a
733 instances will have a ``winrm_client`` attribute containing a
734 ``pypsrp.client.Client`` instance bound to the instance.
734 ``pypsrp.client.Client`` instance bound to the instance.
735 """
735 """
736 if 'IamInstanceProfile' in config:
736 if 'IamInstanceProfile' in config:
737 raise ValueError('IamInstanceProfile cannot be provided in config')
737 raise ValueError('IamInstanceProfile cannot be provided in config')
738 if 'UserData' in config:
738 if 'UserData' in config:
739 raise ValueError('UserData cannot be provided in config')
739 raise ValueError('UserData cannot be provided in config')
740
740
741 password = c.automation.default_password()
741 password = c.automation.default_password()
742
742
743 config = copy.deepcopy(config)
743 config = copy.deepcopy(config)
744 config['IamInstanceProfile'] = {
744 config['IamInstanceProfile'] = {
745 'Name': 'hg-ephemeral-ec2-1',
745 'Name': 'hg-ephemeral-ec2-1',
746 }
746 }
747 config.setdefault('TagSpecifications', []).append(
747 config.setdefault('TagSpecifications', []).append(
748 {
748 {
749 'ResourceType': 'instance',
749 'ResourceType': 'instance',
750 'Tags': [{'Key': 'Name', 'Value': 'hg-temp-windows'}],
750 'Tags': [{'Key': 'Name', 'Value': 'hg-temp-windows'}],
751 }
751 }
752 )
752 )
753
753
754 if bootstrap:
754 if bootstrap:
755 config['UserData'] = WINDOWS_USER_DATA % password
755 config['UserData'] = WINDOWS_USER_DATA % password
756
756
757 with temporary_ec2_instances(c.ec2resource, config) as instances:
757 with temporary_ec2_instances(c.ec2resource, config) as instances:
758 wait_for_ip_addresses(instances)
758 wait_for_ip_addresses(instances)
759
759
760 print('waiting for Windows Remote Management service...')
760 print('waiting for Windows Remote Management service...')
761
761
762 for instance in instances:
762 for instance in instances:
763 client = wait_for_winrm(
763 client = wait_for_winrm(
764 instance.public_ip_address, 'Administrator', password
764 instance.public_ip_address, 'Administrator', password
765 )
765 )
766 print('established WinRM connection to %s' % instance.id)
766 print('established WinRM connection to %s' % instance.id)
767 instance.winrm_client = client
767 instance.winrm_client = client
768
768
769 yield instances
769 yield instances
770
770
771
771
772 def resolve_fingerprint(fingerprint):
772 def resolve_fingerprint(fingerprint):
773 fingerprint = json.dumps(fingerprint, sort_keys=True)
773 fingerprint = json.dumps(fingerprint, sort_keys=True)
774 return hashlib.sha256(fingerprint.encode('utf-8')).hexdigest()
774 return hashlib.sha256(fingerprint.encode('utf-8')).hexdigest()
775
775
776
776
777 def find_and_reconcile_image(ec2resource, name, fingerprint):
777 def find_and_reconcile_image(ec2resource, name, fingerprint):
778 """Attempt to find an existing EC2 AMI with a name and fingerprint.
778 """Attempt to find an existing EC2 AMI with a name and fingerprint.
779
779
780 If an image with the specified fingerprint is found, it is returned.
780 If an image with the specified fingerprint is found, it is returned.
781 Otherwise None is returned.
781 Otherwise None is returned.
782
782
783 Existing images for the specified name that don't have the specified
783 Existing images for the specified name that don't have the specified
784 fingerprint or are missing required metadata or deleted.
784 fingerprint or are missing required metadata or deleted.
785 """
785 """
786 # Find existing AMIs with this name and delete the ones that are invalid.
786 # Find existing AMIs with this name and delete the ones that are invalid.
787 # Store a reference to a good image so it can be returned one the
787 # Store a reference to a good image so it can be returned one the
788 # image state is reconciled.
788 # image state is reconciled.
789 images = ec2resource.images.filter(
789 images = ec2resource.images.filter(
790 Filters=[{'Name': 'name', 'Values': [name]}]
790 Filters=[{'Name': 'name', 'Values': [name]}]
791 )
791 )
792
792
793 existing_image = None
793 existing_image = None
794
794
795 for image in images:
795 for image in images:
796 if image.tags is None:
796 if image.tags is None:
797 print(
797 print(
798 'image %s for %s lacks required tags; removing'
798 'image %s for %s lacks required tags; removing'
799 % (image.id, image.name)
799 % (image.id, image.name)
800 )
800 )
801 remove_ami(ec2resource, image)
801 remove_ami(ec2resource, image)
802 else:
802 else:
803 tags = {t['Key']: t['Value'] for t in image.tags}
803 tags = {t['Key']: t['Value'] for t in image.tags}
804
804
805 if tags.get('HGIMAGEFINGERPRINT') == fingerprint:
805 if tags.get('HGIMAGEFINGERPRINT') == fingerprint:
806 existing_image = image
806 existing_image = image
807 else:
807 else:
808 print(
808 print(
809 'image %s for %s has wrong fingerprint; removing'
809 'image %s for %s has wrong fingerprint; removing'
810 % (image.id, image.name)
810 % (image.id, image.name)
811 )
811 )
812 remove_ami(ec2resource, image)
812 remove_ami(ec2resource, image)
813
813
814 return existing_image
814 return existing_image
815
815
816
816
817 def create_ami_from_instance(
817 def create_ami_from_instance(
818 ec2client, instance, name, description, fingerprint
818 ec2client, instance, name, description, fingerprint
819 ):
819 ):
820 """Create an AMI from a running instance.
820 """Create an AMI from a running instance.
821
821
822 Returns the ``ec2resource.Image`` representing the created AMI.
822 Returns the ``ec2resource.Image`` representing the created AMI.
823 """
823 """
824 instance.stop()
824 instance.stop()
825
825
826 ec2client.get_waiter('instance_stopped').wait(
826 ec2client.get_waiter('instance_stopped').wait(
827 InstanceIds=[instance.id],
827 InstanceIds=[instance.id],
828 WaiterConfig={
828 WaiterConfig={
829 'Delay': 5,
829 'Delay': 5,
830 },
830 },
831 )
831 )
832 print('%s is stopped' % instance.id)
832 print('%s is stopped' % instance.id)
833
833
834 image = instance.create_image(
834 image = instance.create_image(
835 Name=name,
835 Name=name,
836 Description=description,
836 Description=description,
837 )
837 )
838
838
839 image.create_tags(
839 image.create_tags(
840 Tags=[
840 Tags=[
841 {
841 {
842 'Key': 'HGIMAGEFINGERPRINT',
842 'Key': 'HGIMAGEFINGERPRINT',
843 'Value': fingerprint,
843 'Value': fingerprint,
844 },
844 },
845 ]
845 ]
846 )
846 )
847
847
848 print('waiting for image %s' % image.id)
848 print('waiting for image %s' % image.id)
849
849
850 ec2client.get_waiter('image_available').wait(
850 ec2client.get_waiter('image_available').wait(
851 ImageIds=[image.id],
851 ImageIds=[image.id],
852 )
852 )
853
853
854 print('image %s available as %s' % (image.id, image.name))
854 print('image %s available as %s' % (image.id, image.name))
855
855
856 return image
856 return image
857
857
858
858
859 def ensure_linux_dev_ami(c: AWSConnection, distro='debian10', prefix='hg-'):
859 def ensure_linux_dev_ami(c: AWSConnection, distro='debian10', prefix='hg-'):
860 """Ensures a Linux development AMI is available and up-to-date.
860 """Ensures a Linux development AMI is available and up-to-date.
861
861
862 Returns an ``ec2.Image`` of either an existing AMI or a newly-built one.
862 Returns an ``ec2.Image`` of either an existing AMI or a newly-built one.
863 """
863 """
864 ec2client = c.ec2client
864 ec2client = c.ec2client
865 ec2resource = c.ec2resource
865 ec2resource = c.ec2resource
866
866
867 name = '%s%s-%s' % (prefix, 'linux-dev', distro)
867 name = '%s%s-%s' % (prefix, 'linux-dev', distro)
868
868
869 if distro == 'debian9':
869 if distro == 'debian9':
870 image = find_image(
870 image = find_image(
871 ec2resource,
871 ec2resource,
872 DEBIAN_ACCOUNT_ID,
872 DEBIAN_ACCOUNT_ID,
873 'debian-stretch-hvm-x86_64-gp2-2019-09-08-17994',
873 'debian-stretch-hvm-x86_64-gp2-2019-09-08-17994',
874 )
874 )
875 ssh_username = 'admin'
875 ssh_username = 'admin'
876 elif distro == 'debian10':
876 elif distro == 'debian10':
877 image = find_image(
877 image = find_image(
878 ec2resource,
878 ec2resource,
879 DEBIAN_ACCOUNT_ID_2,
879 DEBIAN_ACCOUNT_ID_2,
880 'debian-10-amd64-20190909-10',
880 'debian-10-amd64-20190909-10',
881 )
881 )
882 ssh_username = 'admin'
882 ssh_username = 'admin'
883 elif distro == 'ubuntu18.04':
883 elif distro == 'ubuntu18.04':
884 image = find_image(
884 image = find_image(
885 ec2resource,
885 ec2resource,
886 UBUNTU_ACCOUNT_ID,
886 UBUNTU_ACCOUNT_ID,
887 'ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-20190918',
887 'ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-20190918',
888 )
888 )
889 ssh_username = 'ubuntu'
889 ssh_username = 'ubuntu'
890 elif distro == 'ubuntu19.04':
890 elif distro == 'ubuntu19.04':
891 image = find_image(
891 image = find_image(
892 ec2resource,
892 ec2resource,
893 UBUNTU_ACCOUNT_ID,
893 UBUNTU_ACCOUNT_ID,
894 'ubuntu/images/hvm-ssd/ubuntu-disco-19.04-amd64-server-20190918',
894 'ubuntu/images/hvm-ssd/ubuntu-disco-19.04-amd64-server-20190918',
895 )
895 )
896 ssh_username = 'ubuntu'
896 ssh_username = 'ubuntu'
897 else:
897 else:
898 raise ValueError('unsupported Linux distro: %s' % distro)
898 raise ValueError('unsupported Linux distro: %s' % distro)
899
899
900 config = {
900 config = {
901 'BlockDeviceMappings': [
901 'BlockDeviceMappings': [
902 {
902 {
903 'DeviceName': image.block_device_mappings[0]['DeviceName'],
903 'DeviceName': image.block_device_mappings[0]['DeviceName'],
904 'Ebs': {
904 'Ebs': {
905 'DeleteOnTermination': True,
905 'DeleteOnTermination': True,
906 'VolumeSize': 10,
906 'VolumeSize': 10,
907 'VolumeType': 'gp2',
907 'VolumeType': 'gp2',
908 },
908 },
909 },
909 },
910 ],
910 ],
911 'EbsOptimized': True,
911 'EbsOptimized': True,
912 'ImageId': image.id,
912 'ImageId': image.id,
913 'InstanceInitiatedShutdownBehavior': 'stop',
913 'InstanceInitiatedShutdownBehavior': 'stop',
914 # 8 VCPUs for compiling Python.
914 # 8 VCPUs for compiling Python.
915 'InstanceType': 't3.2xlarge',
915 'InstanceType': 't3.2xlarge',
916 'KeyName': '%sautomation' % prefix,
916 'KeyName': '%sautomation' % prefix,
917 'MaxCount': 1,
917 'MaxCount': 1,
918 'MinCount': 1,
918 'MinCount': 1,
919 'SecurityGroupIds': [c.security_groups['linux-dev-1'].id],
919 'SecurityGroupIds': [c.security_groups['linux-dev-1'].id],
920 }
920 }
921
921
922 requirements2_path = (
922 requirements2_path = (
923 pathlib.Path(__file__).parent.parent / 'linux-requirements-py2.txt'
923 pathlib.Path(__file__).parent.parent / 'linux-requirements-py2.txt'
924 )
924 )
925 requirements3_path = (
925 requirements3_path = (
926 pathlib.Path(__file__).parent.parent / 'linux-requirements-py3.txt'
926 pathlib.Path(__file__).parent.parent / 'linux-requirements-py3.txt'
927 )
927 )
928 requirements35_path = (
929 pathlib.Path(__file__).parent.parent / 'linux-requirements-py3.5.txt'
930 )
928 with requirements2_path.open('r', encoding='utf-8') as fh:
931 with requirements2_path.open('r', encoding='utf-8') as fh:
929 requirements2 = fh.read()
932 requirements2 = fh.read()
930 with requirements3_path.open('r', encoding='utf-8') as fh:
933 with requirements3_path.open('r', encoding='utf-8') as fh:
931 requirements3 = fh.read()
934 requirements3 = fh.read()
935 with requirements35_path.open('r', encoding='utf-8') as fh:
936 requirements35 = fh.read()
932
937
933 # Compute a deterministic fingerprint to determine whether image needs to
938 # Compute a deterministic fingerprint to determine whether image needs to
934 # be regenerated.
939 # be regenerated.
935 fingerprint = resolve_fingerprint(
940 fingerprint = resolve_fingerprint(
936 {
941 {
937 'instance_config': config,
942 'instance_config': config,
938 'bootstrap_script': BOOTSTRAP_DEBIAN,
943 'bootstrap_script': BOOTSTRAP_DEBIAN,
939 'requirements_py2': requirements2,
944 'requirements_py2': requirements2,
940 'requirements_py3': requirements3,
945 'requirements_py3': requirements3,
946 'requirements_py35': requirements35,
941 }
947 }
942 )
948 )
943
949
944 existing_image = find_and_reconcile_image(ec2resource, name, fingerprint)
950 existing_image = find_and_reconcile_image(ec2resource, name, fingerprint)
945
951
946 if existing_image:
952 if existing_image:
947 return existing_image
953 return existing_image
948
954
949 print('no suitable %s image found; creating one...' % name)
955 print('no suitable %s image found; creating one...' % name)
950
956
951 with temporary_ec2_instances(ec2resource, config) as instances:
957 with temporary_ec2_instances(ec2resource, config) as instances:
952 wait_for_ip_addresses(instances)
958 wait_for_ip_addresses(instances)
953
959
954 instance = instances[0]
960 instance = instances[0]
955
961
956 client = wait_for_ssh(
962 client = wait_for_ssh(
957 instance.public_ip_address,
963 instance.public_ip_address,
958 22,
964 22,
959 username=ssh_username,
965 username=ssh_username,
960 key_filename=str(c.key_pair_path_private('automation')),
966 key_filename=str(c.key_pair_path_private('automation')),
961 )
967 )
962
968
963 home = '/home/%s' % ssh_username
969 home = '/home/%s' % ssh_username
964
970
965 with client:
971 with client:
966 print('connecting to SSH server')
972 print('connecting to SSH server')
967 sftp = client.open_sftp()
973 sftp = client.open_sftp()
968
974
969 print('uploading bootstrap files')
975 print('uploading bootstrap files')
970 with sftp.open('%s/bootstrap' % home, 'wb') as fh:
976 with sftp.open('%s/bootstrap' % home, 'wb') as fh:
971 fh.write(BOOTSTRAP_DEBIAN)
977 fh.write(BOOTSTRAP_DEBIAN)
972 fh.chmod(0o0700)
978 fh.chmod(0o0700)
973
979
974 with sftp.open('%s/requirements-py2.txt' % home, 'wb') as fh:
980 with sftp.open('%s/requirements-py2.txt' % home, 'wb') as fh:
975 fh.write(requirements2)
981 fh.write(requirements2)
976 fh.chmod(0o0700)
982 fh.chmod(0o0700)
977
983
978 with sftp.open('%s/requirements-py3.txt' % home, 'wb') as fh:
984 with sftp.open('%s/requirements-py3.txt' % home, 'wb') as fh:
979 fh.write(requirements3)
985 fh.write(requirements3)
980 fh.chmod(0o0700)
986 fh.chmod(0o0700)
981
987
988 with sftp.open('%s/requirements-py3.5.txt' % home, 'wb') as fh:
989 fh.write(requirements35)
990 fh.chmod(0o0700)
991
982 print('executing bootstrap')
992 print('executing bootstrap')
983 chan, stdin, stdout = ssh_exec_command(
993 chan, stdin, stdout = ssh_exec_command(
984 client, '%s/bootstrap' % home
994 client, '%s/bootstrap' % home
985 )
995 )
986 stdin.close()
996 stdin.close()
987
997
988 for line in stdout:
998 for line in stdout:
989 print(line, end='')
999 print(line, end='')
990
1000
991 res = chan.recv_exit_status()
1001 res = chan.recv_exit_status()
992 if res:
1002 if res:
993 raise Exception('non-0 exit from bootstrap: %d' % res)
1003 raise Exception('non-0 exit from bootstrap: %d' % res)
994
1004
995 print(
1005 print(
996 'bootstrap completed; stopping %s to create %s'
1006 'bootstrap completed; stopping %s to create %s'
997 % (instance.id, name)
1007 % (instance.id, name)
998 )
1008 )
999
1009
1000 return create_ami_from_instance(
1010 return create_ami_from_instance(
1001 ec2client,
1011 ec2client,
1002 instance,
1012 instance,
1003 name,
1013 name,
1004 'Mercurial Linux development environment',
1014 'Mercurial Linux development environment',
1005 fingerprint,
1015 fingerprint,
1006 )
1016 )
1007
1017
1008
1018
1009 @contextlib.contextmanager
1019 @contextlib.contextmanager
1010 def temporary_linux_dev_instances(
1020 def temporary_linux_dev_instances(
1011 c: AWSConnection,
1021 c: AWSConnection,
1012 image,
1022 image,
1013 instance_type,
1023 instance_type,
1014 prefix='hg-',
1024 prefix='hg-',
1015 ensure_extra_volume=False,
1025 ensure_extra_volume=False,
1016 ):
1026 ):
1017 """Create temporary Linux development EC2 instances.
1027 """Create temporary Linux development EC2 instances.
1018
1028
1019 Context manager resolves to a list of ``ec2.Instance`` that were created
1029 Context manager resolves to a list of ``ec2.Instance`` that were created
1020 and are running.
1030 and are running.
1021
1031
1022 ``ensure_extra_volume`` can be set to ``True`` to require that instances
1032 ``ensure_extra_volume`` can be set to ``True`` to require that instances
1023 have a 2nd storage volume available other than the primary AMI volume.
1033 have a 2nd storage volume available other than the primary AMI volume.
1024 For instance types with instance storage, this does nothing special.
1034 For instance types with instance storage, this does nothing special.
1025 But for instance types without instance storage, an additional EBS volume
1035 But for instance types without instance storage, an additional EBS volume
1026 will be added to the instance.
1036 will be added to the instance.
1027
1037
1028 Instances have an ``ssh_client`` attribute containing a paramiko SSHClient
1038 Instances have an ``ssh_client`` attribute containing a paramiko SSHClient
1029 instance bound to the instance.
1039 instance bound to the instance.
1030
1040
1031 Instances have an ``ssh_private_key_path`` attributing containing the
1041 Instances have an ``ssh_private_key_path`` attributing containing the
1032 str path to the SSH private key to connect to the instance.
1042 str path to the SSH private key to connect to the instance.
1033 """
1043 """
1034
1044
1035 block_device_mappings = [
1045 block_device_mappings = [
1036 {
1046 {
1037 'DeviceName': image.block_device_mappings[0]['DeviceName'],
1047 'DeviceName': image.block_device_mappings[0]['DeviceName'],
1038 'Ebs': {
1048 'Ebs': {
1039 'DeleteOnTermination': True,
1049 'DeleteOnTermination': True,
1040 'VolumeSize': 12,
1050 'VolumeSize': 12,
1041 'VolumeType': 'gp2',
1051 'VolumeType': 'gp2',
1042 },
1052 },
1043 }
1053 }
1044 ]
1054 ]
1045
1055
1046 # This is not an exhaustive list of instance types having instance storage.
1056 # This is not an exhaustive list of instance types having instance storage.
1047 # But
1057 # But
1048 if ensure_extra_volume and not instance_type.startswith(
1058 if ensure_extra_volume and not instance_type.startswith(
1049 tuple(INSTANCE_TYPES_WITH_STORAGE)
1059 tuple(INSTANCE_TYPES_WITH_STORAGE)
1050 ):
1060 ):
1051 main_device = block_device_mappings[0]['DeviceName']
1061 main_device = block_device_mappings[0]['DeviceName']
1052
1062
1053 if main_device == 'xvda':
1063 if main_device == 'xvda':
1054 second_device = 'xvdb'
1064 second_device = 'xvdb'
1055 elif main_device == '/dev/sda1':
1065 elif main_device == '/dev/sda1':
1056 second_device = '/dev/sdb'
1066 second_device = '/dev/sdb'
1057 else:
1067 else:
1058 raise ValueError(
1068 raise ValueError(
1059 'unhandled primary EBS device name: %s' % main_device
1069 'unhandled primary EBS device name: %s' % main_device
1060 )
1070 )
1061
1071
1062 block_device_mappings.append(
1072 block_device_mappings.append(
1063 {
1073 {
1064 'DeviceName': second_device,
1074 'DeviceName': second_device,
1065 'Ebs': {
1075 'Ebs': {
1066 'DeleteOnTermination': True,
1076 'DeleteOnTermination': True,
1067 'VolumeSize': 8,
1077 'VolumeSize': 8,
1068 'VolumeType': 'gp2',
1078 'VolumeType': 'gp2',
1069 },
1079 },
1070 }
1080 }
1071 )
1081 )
1072
1082
1073 config = {
1083 config = {
1074 'BlockDeviceMappings': block_device_mappings,
1084 'BlockDeviceMappings': block_device_mappings,
1075 'EbsOptimized': True,
1085 'EbsOptimized': True,
1076 'ImageId': image.id,
1086 'ImageId': image.id,
1077 'InstanceInitiatedShutdownBehavior': 'terminate',
1087 'InstanceInitiatedShutdownBehavior': 'terminate',
1078 'InstanceType': instance_type,
1088 'InstanceType': instance_type,
1079 'KeyName': '%sautomation' % prefix,
1089 'KeyName': '%sautomation' % prefix,
1080 'MaxCount': 1,
1090 'MaxCount': 1,
1081 'MinCount': 1,
1091 'MinCount': 1,
1082 'SecurityGroupIds': [c.security_groups['linux-dev-1'].id],
1092 'SecurityGroupIds': [c.security_groups['linux-dev-1'].id],
1083 }
1093 }
1084
1094
1085 with temporary_ec2_instances(c.ec2resource, config) as instances:
1095 with temporary_ec2_instances(c.ec2resource, config) as instances:
1086 wait_for_ip_addresses(instances)
1096 wait_for_ip_addresses(instances)
1087
1097
1088 ssh_private_key_path = str(c.key_pair_path_private('automation'))
1098 ssh_private_key_path = str(c.key_pair_path_private('automation'))
1089
1099
1090 for instance in instances:
1100 for instance in instances:
1091 client = wait_for_ssh(
1101 client = wait_for_ssh(
1092 instance.public_ip_address,
1102 instance.public_ip_address,
1093 22,
1103 22,
1094 username='hg',
1104 username='hg',
1095 key_filename=ssh_private_key_path,
1105 key_filename=ssh_private_key_path,
1096 )
1106 )
1097
1107
1098 instance.ssh_client = client
1108 instance.ssh_client = client
1099 instance.ssh_private_key_path = ssh_private_key_path
1109 instance.ssh_private_key_path = ssh_private_key_path
1100
1110
1101 try:
1111 try:
1102 yield instances
1112 yield instances
1103 finally:
1113 finally:
1104 for instance in instances:
1114 for instance in instances:
1105 instance.ssh_client.close()
1115 instance.ssh_client.close()
1106
1116
1107
1117
1108 def ensure_windows_dev_ami(
1118 def ensure_windows_dev_ami(
1109 c: AWSConnection,
1119 c: AWSConnection,
1110 prefix='hg-',
1120 prefix='hg-',
1111 base_image_name=WINDOWS_BASE_IMAGE_NAME,
1121 base_image_name=WINDOWS_BASE_IMAGE_NAME,
1112 ):
1122 ):
1113 """Ensure Windows Development AMI is available and up-to-date.
1123 """Ensure Windows Development AMI is available and up-to-date.
1114
1124
1115 If necessary, a modern AMI will be built by starting a temporary EC2
1125 If necessary, a modern AMI will be built by starting a temporary EC2
1116 instance and bootstrapping it.
1126 instance and bootstrapping it.
1117
1127
1118 Obsolete AMIs will be deleted so there is only a single AMI having the
1128 Obsolete AMIs will be deleted so there is only a single AMI having the
1119 desired name.
1129 desired name.
1120
1130
1121 Returns an ``ec2.Image`` of either an existing AMI or a newly-built
1131 Returns an ``ec2.Image`` of either an existing AMI or a newly-built
1122 one.
1132 one.
1123 """
1133 """
1124 ec2client = c.ec2client
1134 ec2client = c.ec2client
1125 ec2resource = c.ec2resource
1135 ec2resource = c.ec2resource
1126 ssmclient = c.session.client('ssm')
1136 ssmclient = c.session.client('ssm')
1127
1137
1128 name = '%s%s' % (prefix, 'windows-dev')
1138 name = '%s%s' % (prefix, 'windows-dev')
1129
1139
1130 image = find_image(
1140 image = find_image(
1131 ec2resource,
1141 ec2resource,
1132 AMAZON_ACCOUNT_ID,
1142 AMAZON_ACCOUNT_ID,
1133 base_image_name,
1143 base_image_name,
1134 reverse_sort_field="name",
1144 reverse_sort_field="name",
1135 )
1145 )
1136
1146
1137 config = {
1147 config = {
1138 'BlockDeviceMappings': [
1148 'BlockDeviceMappings': [
1139 {
1149 {
1140 'DeviceName': '/dev/sda1',
1150 'DeviceName': '/dev/sda1',
1141 'Ebs': {
1151 'Ebs': {
1142 'DeleteOnTermination': True,
1152 'DeleteOnTermination': True,
1143 'VolumeSize': 32,
1153 'VolumeSize': 32,
1144 'VolumeType': 'gp2',
1154 'VolumeType': 'gp2',
1145 },
1155 },
1146 }
1156 }
1147 ],
1157 ],
1148 'ImageId': image.id,
1158 'ImageId': image.id,
1149 'InstanceInitiatedShutdownBehavior': 'stop',
1159 'InstanceInitiatedShutdownBehavior': 'stop',
1150 'InstanceType': 't3.medium',
1160 'InstanceType': 't3.medium',
1151 'KeyName': '%sautomation' % prefix,
1161 'KeyName': '%sautomation' % prefix,
1152 'MaxCount': 1,
1162 'MaxCount': 1,
1153 'MinCount': 1,
1163 'MinCount': 1,
1154 'SecurityGroupIds': [c.security_groups['windows-dev-1'].id],
1164 'SecurityGroupIds': [c.security_groups['windows-dev-1'].id],
1155 }
1165 }
1156
1166
1157 commands = [
1167 commands = [
1158 # Need to start the service so sshd_config is generated.
1168 # Need to start the service so sshd_config is generated.
1159 'Start-Service sshd',
1169 'Start-Service sshd',
1160 'Write-Output "modifying sshd_config"',
1170 'Write-Output "modifying sshd_config"',
1161 r'$content = Get-Content C:\ProgramData\ssh\sshd_config',
1171 r'$content = Get-Content C:\ProgramData\ssh\sshd_config',
1162 '$content = $content -replace "Match Group administrators","" -replace "AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys",""',
1172 '$content = $content -replace "Match Group administrators","" -replace "AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys",""',
1163 r'$content | Set-Content C:\ProgramData\ssh\sshd_config',
1173 r'$content | Set-Content C:\ProgramData\ssh\sshd_config',
1164 'Import-Module OpenSSHUtils',
1174 'Import-Module OpenSSHUtils',
1165 r'Repair-SshdConfigPermission C:\ProgramData\ssh\sshd_config -Confirm:$false',
1175 r'Repair-SshdConfigPermission C:\ProgramData\ssh\sshd_config -Confirm:$false',
1166 'Restart-Service sshd',
1176 'Restart-Service sshd',
1167 'Write-Output "installing OpenSSL client"',
1177 'Write-Output "installing OpenSSL client"',
1168 'Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0',
1178 'Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0',
1169 'Set-Service -Name sshd -StartupType "Automatic"',
1179 'Set-Service -Name sshd -StartupType "Automatic"',
1170 'Write-Output "OpenSSH server running"',
1180 'Write-Output "OpenSSH server running"',
1171 ]
1181 ]
1172
1182
1173 with INSTALL_WINDOWS_DEPENDENCIES.open('r', encoding='utf-8') as fh:
1183 with INSTALL_WINDOWS_DEPENDENCIES.open('r', encoding='utf-8') as fh:
1174 commands.extend(l.rstrip() for l in fh)
1184 commands.extend(l.rstrip() for l in fh)
1175
1185
1176 # Schedule run of EC2Launch on next boot. This ensures that UserData
1186 # Schedule run of EC2Launch on next boot. This ensures that UserData
1177 # is executed.
1187 # is executed.
1178 # We disable setComputerName because it forces a reboot.
1188 # We disable setComputerName because it forces a reboot.
1179 # We set an explicit admin password because this causes UserData to run
1189 # We set an explicit admin password because this causes UserData to run
1180 # as Administrator instead of System.
1190 # as Administrator instead of System.
1181 commands.extend(
1191 commands.extend(
1182 [
1192 [
1183 r'''Set-Content -Path C:\ProgramData\Amazon\EC2-Windows\Launch\Config\LaunchConfig.json '''
1193 r'''Set-Content -Path C:\ProgramData\Amazon\EC2-Windows\Launch\Config\LaunchConfig.json '''
1184 r'''-Value '{"setComputerName": false, "setWallpaper": true, "addDnsSuffixList": true, '''
1194 r'''-Value '{"setComputerName": false, "setWallpaper": true, "addDnsSuffixList": true, '''
1185 r'''"extendBootVolumeSize": true, "handleUserData": true, '''
1195 r'''"extendBootVolumeSize": true, "handleUserData": true, '''
1186 r'''"adminPasswordType": "Specify", "adminPassword": "%s"}' '''
1196 r'''"adminPasswordType": "Specify", "adminPassword": "%s"}' '''
1187 % c.automation.default_password(),
1197 % c.automation.default_password(),
1188 r'C:\ProgramData\Amazon\EC2-Windows\Launch\Scripts\InitializeInstance.ps1 '
1198 r'C:\ProgramData\Amazon\EC2-Windows\Launch\Scripts\InitializeInstance.ps1 '
1189 r'–Schedule',
1199 r'–Schedule',
1190 ]
1200 ]
1191 )
1201 )
1192
1202
1193 # Disable Windows Defender when bootstrapping because it just slows
1203 # Disable Windows Defender when bootstrapping because it just slows
1194 # things down.
1204 # things down.
1195 commands.insert(0, 'Set-MpPreference -DisableRealtimeMonitoring $true')
1205 commands.insert(0, 'Set-MpPreference -DisableRealtimeMonitoring $true')
1196 commands.append('Set-MpPreference -DisableRealtimeMonitoring $false')
1206 commands.append('Set-MpPreference -DisableRealtimeMonitoring $false')
1197
1207
1198 # Compute a deterministic fingerprint to determine whether image needs
1208 # Compute a deterministic fingerprint to determine whether image needs
1199 # to be regenerated.
1209 # to be regenerated.
1200 fingerprint = resolve_fingerprint(
1210 fingerprint = resolve_fingerprint(
1201 {
1211 {
1202 'instance_config': config,
1212 'instance_config': config,
1203 'user_data': WINDOWS_USER_DATA,
1213 'user_data': WINDOWS_USER_DATA,
1204 'initial_bootstrap': WINDOWS_BOOTSTRAP_POWERSHELL,
1214 'initial_bootstrap': WINDOWS_BOOTSTRAP_POWERSHELL,
1205 'bootstrap_commands': commands,
1215 'bootstrap_commands': commands,
1206 'base_image_name': base_image_name,
1216 'base_image_name': base_image_name,
1207 }
1217 }
1208 )
1218 )
1209
1219
1210 existing_image = find_and_reconcile_image(ec2resource, name, fingerprint)
1220 existing_image = find_and_reconcile_image(ec2resource, name, fingerprint)
1211
1221
1212 if existing_image:
1222 if existing_image:
1213 return existing_image
1223 return existing_image
1214
1224
1215 print('no suitable Windows development image found; creating one...')
1225 print('no suitable Windows development image found; creating one...')
1216
1226
1217 with create_temp_windows_ec2_instances(
1227 with create_temp_windows_ec2_instances(
1218 c, config, bootstrap=True
1228 c, config, bootstrap=True
1219 ) as instances:
1229 ) as instances:
1220 assert len(instances) == 1
1230 assert len(instances) == 1
1221 instance = instances[0]
1231 instance = instances[0]
1222
1232
1223 wait_for_ssm(ssmclient, [instance])
1233 wait_for_ssm(ssmclient, [instance])
1224
1234
1225 # On first boot, install various Windows updates.
1235 # On first boot, install various Windows updates.
1226 # We would ideally use PowerShell Remoting for this. However, there are
1236 # We would ideally use PowerShell Remoting for this. However, there are
1227 # trust issues that make it difficult to invoke Windows Update
1237 # trust issues that make it difficult to invoke Windows Update
1228 # remotely. So we use SSM, which has a mechanism for running Windows
1238 # remotely. So we use SSM, which has a mechanism for running Windows
1229 # Update.
1239 # Update.
1230 print('installing Windows features...')
1240 print('installing Windows features...')
1231 run_ssm_command(
1241 run_ssm_command(
1232 ssmclient,
1242 ssmclient,
1233 [instance],
1243 [instance],
1234 'AWS-RunPowerShellScript',
1244 'AWS-RunPowerShellScript',
1235 {
1245 {
1236 'commands': WINDOWS_BOOTSTRAP_POWERSHELL.split('\n'),
1246 'commands': WINDOWS_BOOTSTRAP_POWERSHELL.split('\n'),
1237 },
1247 },
1238 )
1248 )
1239
1249
1240 # Reboot so all updates are fully applied.
1250 # Reboot so all updates are fully applied.
1241 #
1251 #
1242 # We don't use instance.reboot() here because it is asynchronous and
1252 # We don't use instance.reboot() here because it is asynchronous and
1243 # we don't know when exactly the instance has rebooted. It could take
1253 # we don't know when exactly the instance has rebooted. It could take
1244 # a while to stop and we may start trying to interact with the instance
1254 # a while to stop and we may start trying to interact with the instance
1245 # before it has rebooted.
1255 # before it has rebooted.
1246 print('rebooting instance %s' % instance.id)
1256 print('rebooting instance %s' % instance.id)
1247 instance.stop()
1257 instance.stop()
1248 ec2client.get_waiter('instance_stopped').wait(
1258 ec2client.get_waiter('instance_stopped').wait(
1249 InstanceIds=[instance.id],
1259 InstanceIds=[instance.id],
1250 WaiterConfig={
1260 WaiterConfig={
1251 'Delay': 5,
1261 'Delay': 5,
1252 },
1262 },
1253 )
1263 )
1254
1264
1255 instance.start()
1265 instance.start()
1256 wait_for_ip_addresses([instance])
1266 wait_for_ip_addresses([instance])
1257
1267
1258 # There is a race condition here between the User Data PS script running
1268 # There is a race condition here between the User Data PS script running
1259 # and us connecting to WinRM. This can manifest as
1269 # and us connecting to WinRM. This can manifest as
1260 # "AuthorizationManager check failed" failures during run_powershell().
1270 # "AuthorizationManager check failed" failures during run_powershell().
1261 # TODO figure out a workaround.
1271 # TODO figure out a workaround.
1262
1272
1263 print('waiting for Windows Remote Management to come back...')
1273 print('waiting for Windows Remote Management to come back...')
1264 client = wait_for_winrm(
1274 client = wait_for_winrm(
1265 instance.public_ip_address,
1275 instance.public_ip_address,
1266 'Administrator',
1276 'Administrator',
1267 c.automation.default_password(),
1277 c.automation.default_password(),
1268 )
1278 )
1269 print('established WinRM connection to %s' % instance.id)
1279 print('established WinRM connection to %s' % instance.id)
1270 instance.winrm_client = client
1280 instance.winrm_client = client
1271
1281
1272 print('bootstrapping instance...')
1282 print('bootstrapping instance...')
1273 run_powershell(instance.winrm_client, '\n'.join(commands))
1283 run_powershell(instance.winrm_client, '\n'.join(commands))
1274
1284
1275 print('bootstrap completed; stopping %s to create image' % instance.id)
1285 print('bootstrap completed; stopping %s to create image' % instance.id)
1276 return create_ami_from_instance(
1286 return create_ami_from_instance(
1277 ec2client,
1287 ec2client,
1278 instance,
1288 instance,
1279 name,
1289 name,
1280 'Mercurial Windows development environment',
1290 'Mercurial Windows development environment',
1281 fingerprint,
1291 fingerprint,
1282 )
1292 )
1283
1293
1284
1294
1285 @contextlib.contextmanager
1295 @contextlib.contextmanager
1286 def temporary_windows_dev_instances(
1296 def temporary_windows_dev_instances(
1287 c: AWSConnection,
1297 c: AWSConnection,
1288 image,
1298 image,
1289 instance_type,
1299 instance_type,
1290 prefix='hg-',
1300 prefix='hg-',
1291 disable_antivirus=False,
1301 disable_antivirus=False,
1292 ):
1302 ):
1293 """Create a temporary Windows development EC2 instance.
1303 """Create a temporary Windows development EC2 instance.
1294
1304
1295 Context manager resolves to the list of ``EC2.Instance`` that were created.
1305 Context manager resolves to the list of ``EC2.Instance`` that were created.
1296 """
1306 """
1297 config = {
1307 config = {
1298 'BlockDeviceMappings': [
1308 'BlockDeviceMappings': [
1299 {
1309 {
1300 'DeviceName': '/dev/sda1',
1310 'DeviceName': '/dev/sda1',
1301 'Ebs': {
1311 'Ebs': {
1302 'DeleteOnTermination': True,
1312 'DeleteOnTermination': True,
1303 'VolumeSize': 32,
1313 'VolumeSize': 32,
1304 'VolumeType': 'gp2',
1314 'VolumeType': 'gp2',
1305 },
1315 },
1306 }
1316 }
1307 ],
1317 ],
1308 'ImageId': image.id,
1318 'ImageId': image.id,
1309 'InstanceInitiatedShutdownBehavior': 'stop',
1319 'InstanceInitiatedShutdownBehavior': 'stop',
1310 'InstanceType': instance_type,
1320 'InstanceType': instance_type,
1311 'KeyName': '%sautomation' % prefix,
1321 'KeyName': '%sautomation' % prefix,
1312 'MaxCount': 1,
1322 'MaxCount': 1,
1313 'MinCount': 1,
1323 'MinCount': 1,
1314 'SecurityGroupIds': [c.security_groups['windows-dev-1'].id],
1324 'SecurityGroupIds': [c.security_groups['windows-dev-1'].id],
1315 }
1325 }
1316
1326
1317 with create_temp_windows_ec2_instances(c, config) as instances:
1327 with create_temp_windows_ec2_instances(c, config) as instances:
1318 if disable_antivirus:
1328 if disable_antivirus:
1319 for instance in instances:
1329 for instance in instances:
1320 run_powershell(
1330 run_powershell(
1321 instance.winrm_client,
1331 instance.winrm_client,
1322 'Set-MpPreference -DisableRealtimeMonitoring $true',
1332 'Set-MpPreference -DisableRealtimeMonitoring $true',
1323 )
1333 )
1324
1334
1325 yield instances
1335 yield instances
@@ -1,597 +1,609
1 # linux.py - Linux specific automation functionality
1 # linux.py - Linux specific automation functionality
2 #
2 #
3 # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 # no-check-code because Python 3 native.
8 # no-check-code because Python 3 native.
9
9
10 import os
10 import os
11 import pathlib
11 import pathlib
12 import shlex
12 import shlex
13 import subprocess
13 import subprocess
14 import tempfile
14 import tempfile
15
15
16 from .ssh import exec_command
16 from .ssh import exec_command
17
17
18
18
19 # Linux distributions that are supported.
19 # Linux distributions that are supported.
20 DISTROS = {
20 DISTROS = {
21 'debian9',
21 'debian9',
22 'debian10',
22 'debian10',
23 'ubuntu18.04',
23 'ubuntu18.04',
24 'ubuntu19.04',
24 'ubuntu19.04',
25 }
25 }
26
26
27 INSTALL_PYTHONS = r'''
27 INSTALL_PYTHONS = r'''
28 PYENV2_VERSIONS="2.7.17 pypy2.7-7.2.0"
28 PYENV2_VERSIONS="2.7.17 pypy2.7-7.2.0"
29 PYENV3_VERSIONS="3.5.10 3.6.12 3.7.9 3.8.6 3.9.0 pypy3.5-7.0.0 pypy3.6-7.3.0"
29 PYENV3_VERSIONS="3.5.10 3.6.12 3.7.9 3.8.6 3.9.0 pypy3.5-7.0.0 pypy3.6-7.3.0"
30
30
31 git clone https://github.com/pyenv/pyenv.git /hgdev/pyenv
31 git clone https://github.com/pyenv/pyenv.git /hgdev/pyenv
32 pushd /hgdev/pyenv
32 pushd /hgdev/pyenv
33 git checkout 8ac91b4fd678a8c04356f5ec85cfcd565c265e9a
33 git checkout 8ac91b4fd678a8c04356f5ec85cfcd565c265e9a
34 popd
34 popd
35
35
36 export PYENV_ROOT="/hgdev/pyenv"
36 export PYENV_ROOT="/hgdev/pyenv"
37 export PATH="$PYENV_ROOT/bin:$PATH"
37 export PATH="$PYENV_ROOT/bin:$PATH"
38
38
39 # pip 19.2.3.
39 # pip 19.2.3.
40 PIP_SHA256=57e3643ff19f018f8a00dfaa6b7e4620e3c1a7a2171fd218425366ec006b3bfe
40 PIP_SHA256=57e3643ff19f018f8a00dfaa6b7e4620e3c1a7a2171fd218425366ec006b3bfe
41 wget -O get-pip.py --progress dot:mega https://github.com/pypa/get-pip/raw/309a56c5fd94bd1134053a541cb4657a4e47e09d/get-pip.py
41 wget -O get-pip.py --progress dot:mega https://github.com/pypa/get-pip/raw/309a56c5fd94bd1134053a541cb4657a4e47e09d/get-pip.py
42 echo "${PIP_SHA256} get-pip.py" | sha256sum --check -
42 echo "${PIP_SHA256} get-pip.py" | sha256sum --check -
43
43
44 VIRTUALENV_SHA256=f78d81b62d3147396ac33fc9d77579ddc42cc2a98dd9ea38886f616b33bc7fb2
44 VIRTUALENV_SHA256=f78d81b62d3147396ac33fc9d77579ddc42cc2a98dd9ea38886f616b33bc7fb2
45 VIRTUALENV_TARBALL=virtualenv-16.7.5.tar.gz
45 VIRTUALENV_TARBALL=virtualenv-16.7.5.tar.gz
46 wget -O ${VIRTUALENV_TARBALL} --progress dot:mega https://files.pythonhosted.org/packages/66/f0/6867af06d2e2f511e4e1d7094ff663acdebc4f15d4a0cb0fed1007395124/${VIRTUALENV_TARBALL}
46 wget -O ${VIRTUALENV_TARBALL} --progress dot:mega https://files.pythonhosted.org/packages/66/f0/6867af06d2e2f511e4e1d7094ff663acdebc4f15d4a0cb0fed1007395124/${VIRTUALENV_TARBALL}
47 echo "${VIRTUALENV_SHA256} ${VIRTUALENV_TARBALL}" | sha256sum --check -
47 echo "${VIRTUALENV_SHA256} ${VIRTUALENV_TARBALL}" | sha256sum --check -
48
48
49 for v in ${PYENV2_VERSIONS}; do
49 for v in ${PYENV2_VERSIONS}; do
50 pyenv install -v ${v}
50 pyenv install -v ${v}
51 ${PYENV_ROOT}/versions/${v}/bin/python get-pip.py
51 ${PYENV_ROOT}/versions/${v}/bin/python get-pip.py
52 ${PYENV_ROOT}/versions/${v}/bin/pip install ${VIRTUALENV_TARBALL}
52 ${PYENV_ROOT}/versions/${v}/bin/pip install ${VIRTUALENV_TARBALL}
53 ${PYENV_ROOT}/versions/${v}/bin/pip install -r /hgdev/requirements-py2.txt
53 ${PYENV_ROOT}/versions/${v}/bin/pip install -r /hgdev/requirements-py2.txt
54 done
54 done
55
55
56 for v in ${PYENV3_VERSIONS}; do
56 for v in ${PYENV3_VERSIONS}; do
57 pyenv install -v ${v}
57 pyenv install -v ${v}
58 ${PYENV_ROOT}/versions/${v}/bin/python get-pip.py
58 ${PYENV_ROOT}/versions/${v}/bin/python get-pip.py
59 ${PYENV_ROOT}/versions/${v}/bin/pip install -r /hgdev/requirements-py3.txt
59
60 case ${v} in
61 3.5.*)
62 REQUIREMENTS=requirements-py3.5.txt
63 ;;
64 pypy3.5*)
65 REQUIREMENTS=requirements-py3.5.txt
66 ;;
67 *)
68 REQUIREMENTS=requirements-py3.txt
69 ;;
70 esac
71
72 ${PYENV_ROOT}/versions/${v}/bin/pip install -r /hgdev/${REQUIREMENTS}
60 done
73 done
61
74
62 pyenv global ${PYENV2_VERSIONS} ${PYENV3_VERSIONS} system
75 pyenv global ${PYENV2_VERSIONS} ${PYENV3_VERSIONS} system
63 '''.lstrip().replace(
76 '''.lstrip().replace(
64 '\r\n', '\n'
77 '\r\n', '\n'
65 )
78 )
66
79
67
80
68 INSTALL_RUST = r'''
81 INSTALL_RUST = r'''
69 RUSTUP_INIT_SHA256=a46fe67199b7bcbbde2dcbc23ae08db6f29883e260e23899a88b9073effc9076
82 RUSTUP_INIT_SHA256=a46fe67199b7bcbbde2dcbc23ae08db6f29883e260e23899a88b9073effc9076
70 wget -O rustup-init --progress dot:mega https://static.rust-lang.org/rustup/archive/1.18.3/x86_64-unknown-linux-gnu/rustup-init
83 wget -O rustup-init --progress dot:mega https://static.rust-lang.org/rustup/archive/1.18.3/x86_64-unknown-linux-gnu/rustup-init
71 echo "${RUSTUP_INIT_SHA256} rustup-init" | sha256sum --check -
84 echo "${RUSTUP_INIT_SHA256} rustup-init" | sha256sum --check -
72
85
73 chmod +x rustup-init
86 chmod +x rustup-init
74 sudo -H -u hg -g hg ./rustup-init -y
87 sudo -H -u hg -g hg ./rustup-init -y
75 sudo -H -u hg -g hg /home/hg/.cargo/bin/rustup install 1.31.1 1.46.0
88 sudo -H -u hg -g hg /home/hg/.cargo/bin/rustup install 1.31.1 1.46.0
76 sudo -H -u hg -g hg /home/hg/.cargo/bin/rustup component add clippy
89 sudo -H -u hg -g hg /home/hg/.cargo/bin/rustup component add clippy
77
90
78 sudo -H -u hg -g hg /home/hg/.cargo/bin/cargo install --version 0.10.3 pyoxidizer
91 sudo -H -u hg -g hg /home/hg/.cargo/bin/cargo install --version 0.10.3 pyoxidizer
79 '''
92 '''
80
93
81
94
82 BOOTSTRAP_VIRTUALENV = r'''
95 BOOTSTRAP_VIRTUALENV = r'''
83 /usr/bin/virtualenv /hgdev/venv-bootstrap
96 /usr/bin/virtualenv /hgdev/venv-bootstrap
84
97
85 HG_SHA256=35fc8ba5e0379c1b3affa2757e83fb0509e8ac314cbd9f1fd133cf265d16e49f
98 HG_SHA256=35fc8ba5e0379c1b3affa2757e83fb0509e8ac314cbd9f1fd133cf265d16e49f
86 HG_TARBALL=mercurial-5.1.1.tar.gz
99 HG_TARBALL=mercurial-5.1.1.tar.gz
87
100
88 wget -O ${HG_TARBALL} --progress dot:mega https://www.mercurial-scm.org/release/${HG_TARBALL}
101 wget -O ${HG_TARBALL} --progress dot:mega https://www.mercurial-scm.org/release/${HG_TARBALL}
89 echo "${HG_SHA256} ${HG_TARBALL}" | sha256sum --check -
102 echo "${HG_SHA256} ${HG_TARBALL}" | sha256sum --check -
90
103
91 /hgdev/venv-bootstrap/bin/pip install ${HG_TARBALL}
104 /hgdev/venv-bootstrap/bin/pip install ${HG_TARBALL}
92 '''.lstrip().replace(
105 '''.lstrip().replace(
93 '\r\n', '\n'
106 '\r\n', '\n'
94 )
107 )
95
108
96
109
97 BOOTSTRAP_DEBIAN = (
110 BOOTSTRAP_DEBIAN = (
98 r'''
111 r'''
99 #!/bin/bash
112 #!/bin/bash
100
113
101 set -ex
114 set -ex
102
115
103 DISTRO=`grep DISTRIB_ID /etc/lsb-release | awk -F= '{{print $2}}'`
116 DISTRO=`grep DISTRIB_ID /etc/lsb-release | awk -F= '{{print $2}}'`
104 DEBIAN_VERSION=`cat /etc/debian_version`
117 DEBIAN_VERSION=`cat /etc/debian_version`
105 LSB_RELEASE=`lsb_release -cs`
118 LSB_RELEASE=`lsb_release -cs`
106
119
107 sudo /usr/sbin/groupadd hg
120 sudo /usr/sbin/groupadd hg
108 sudo /usr/sbin/groupadd docker
121 sudo /usr/sbin/groupadd docker
109 sudo /usr/sbin/useradd -g hg -G sudo,docker -d /home/hg -m -s /bin/bash hg
122 sudo /usr/sbin/useradd -g hg -G sudo,docker -d /home/hg -m -s /bin/bash hg
110 sudo mkdir /home/hg/.ssh
123 sudo mkdir /home/hg/.ssh
111 sudo cp ~/.ssh/authorized_keys /home/hg/.ssh/authorized_keys
124 sudo cp ~/.ssh/authorized_keys /home/hg/.ssh/authorized_keys
112 sudo chown -R hg:hg /home/hg/.ssh
125 sudo chown -R hg:hg /home/hg/.ssh
113 sudo chmod 700 /home/hg/.ssh
126 sudo chmod 700 /home/hg/.ssh
114 sudo chmod 600 /home/hg/.ssh/authorized_keys
127 sudo chmod 600 /home/hg/.ssh/authorized_keys
115
128
116 cat << EOF | sudo tee /etc/sudoers.d/90-hg
129 cat << EOF | sudo tee /etc/sudoers.d/90-hg
117 hg ALL=(ALL) NOPASSWD:ALL
130 hg ALL=(ALL) NOPASSWD:ALL
118 EOF
131 EOF
119
132
120 sudo apt-get update
133 sudo apt-get update
121 sudo DEBIAN_FRONTEND=noninteractive apt-get -yq dist-upgrade
134 sudo DEBIAN_FRONTEND=noninteractive apt-get -yq dist-upgrade
122
135
123 # Install packages necessary to set up Docker Apt repo.
136 # Install packages necessary to set up Docker Apt repo.
124 sudo DEBIAN_FRONTEND=noninteractive apt-get -yq install --no-install-recommends \
137 sudo DEBIAN_FRONTEND=noninteractive apt-get -yq install --no-install-recommends \
125 apt-transport-https \
138 apt-transport-https \
126 gnupg
139 gnupg
127
140
128 cat > docker-apt-key << EOF
141 cat > docker-apt-key << EOF
129 -----BEGIN PGP PUBLIC KEY BLOCK-----
142 -----BEGIN PGP PUBLIC KEY BLOCK-----
130
143
131 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
188 YT90qFF93M3v01BbxP+EIY2/9tiIPbrd
201 YT90qFF93M3v01BbxP+EIY2/9tiIPbrd
189 =0YYh
202 =0YYh
190 -----END PGP PUBLIC KEY BLOCK-----
203 -----END PGP PUBLIC KEY BLOCK-----
191 EOF
204 EOF
192
205
193 sudo apt-key add docker-apt-key
206 sudo apt-key add docker-apt-key
194
207
195 if [ "$LSB_RELEASE" = "stretch" ]; then
208 if [ "$LSB_RELEASE" = "stretch" ]; then
196 cat << EOF | sudo tee -a /etc/apt/sources.list
209 cat << EOF | sudo tee -a /etc/apt/sources.list
197 # Need backports for clang-format-6.0
210 # Need backports for clang-format-6.0
198 deb http://deb.debian.org/debian stretch-backports main
211 deb http://deb.debian.org/debian stretch-backports main
199 EOF
212 EOF
200 fi
213 fi
201
214
202 if [ "$LSB_RELEASE" = "stretch" -o "$LSB_RELEASE" = "buster" ]; then
215 if [ "$LSB_RELEASE" = "stretch" -o "$LSB_RELEASE" = "buster" ]; then
203 cat << EOF | sudo tee -a /etc/apt/sources.list
216 cat << EOF | sudo tee -a /etc/apt/sources.list
204 # Sources are useful if we want to compile things locally.
217 # Sources are useful if we want to compile things locally.
205 deb-src http://deb.debian.org/debian $LSB_RELEASE main
218 deb-src http://deb.debian.org/debian $LSB_RELEASE main
206 deb-src http://security.debian.org/debian-security $LSB_RELEASE/updates main
219 deb-src http://security.debian.org/debian-security $LSB_RELEASE/updates main
207 deb-src http://deb.debian.org/debian $LSB_RELEASE-updates main
220 deb-src http://deb.debian.org/debian $LSB_RELEASE-updates main
208 deb-src http://deb.debian.org/debian $LSB_RELEASE-backports main
221 deb-src http://deb.debian.org/debian $LSB_RELEASE-backports main
209
222
210 deb [arch=amd64] https://download.docker.com/linux/debian $LSB_RELEASE stable
223 deb [arch=amd64] https://download.docker.com/linux/debian $LSB_RELEASE stable
211 EOF
224 EOF
212
225
213 elif [ "$DISTRO" = "Ubuntu" ]; then
226 elif [ "$DISTRO" = "Ubuntu" ]; then
214 cat << EOF | sudo tee -a /etc/apt/sources.list
227 cat << EOF | sudo tee -a /etc/apt/sources.list
215 deb [arch=amd64] https://download.docker.com/linux/ubuntu $LSB_RELEASE stable
228 deb [arch=amd64] https://download.docker.com/linux/ubuntu $LSB_RELEASE stable
216 EOF
229 EOF
217
230
218 fi
231 fi
219
232
220 sudo apt-get update
233 sudo apt-get update
221
234
222 PACKAGES="\
235 PACKAGES="\
223 awscli \
236 awscli \
224 btrfs-progs \
237 btrfs-progs \
225 build-essential \
238 build-essential \
226 bzr \
239 bzr \
227 clang-format-6.0 \
240 clang-format-6.0 \
228 cvs \
241 cvs \
229 darcs \
242 darcs \
230 debhelper \
243 debhelper \
231 devscripts \
244 devscripts \
232 docker-ce \
245 docker-ce \
233 dpkg-dev \
246 dpkg-dev \
234 dstat \
247 dstat \
235 emacs \
248 emacs \
236 gettext \
249 gettext \
237 git \
250 git \
238 htop \
251 htop \
239 iotop \
252 iotop \
240 jfsutils \
253 jfsutils \
241 libbz2-dev \
254 libbz2-dev \
242 libexpat1-dev \
255 libexpat1-dev \
243 libffi-dev \
256 libffi-dev \
244 libgdbm-dev \
257 libgdbm-dev \
245 liblzma-dev \
258 liblzma-dev \
246 libncurses5-dev \
259 libncurses5-dev \
247 libnss3-dev \
260 libnss3-dev \
248 libreadline-dev \
261 libreadline-dev \
249 libsqlite3-dev \
262 libsqlite3-dev \
250 libssl-dev \
263 libssl-dev \
251 netbase \
264 netbase \
252 ntfs-3g \
265 ntfs-3g \
253 nvme-cli \
266 nvme-cli \
254 pyflakes \
267 pyflakes \
255 pyflakes3 \
268 pyflakes3 \
256 pylint \
269 pylint \
257 pylint3 \
270 pylint3 \
258 python-all-dev \
271 python-all-dev \
259 python-dev \
272 python-dev \
260 python-docutils \
273 python-docutils \
261 python-fuzzywuzzy \
274 python-fuzzywuzzy \
262 python-pygments \
275 python-pygments \
263 python-subversion \
276 python-subversion \
264 python-vcr \
277 python-vcr \
265 python3-boto3 \
278 python3-boto3 \
266 python3-dev \
279 python3-dev \
267 python3-docutils \
280 python3-docutils \
268 python3-fuzzywuzzy \
281 python3-fuzzywuzzy \
269 python3-pygments \
282 python3-pygments \
270 python3-vcr \
283 python3-vcr \
271 python3-venv \
284 python3-venv \
272 rsync \
285 rsync \
273 sqlite3 \
286 sqlite3 \
274 subversion \
287 subversion \
275 tcl-dev \
288 tcl-dev \
276 tk-dev \
289 tk-dev \
277 tla \
290 tla \
278 unzip \
291 unzip \
279 uuid-dev \
292 uuid-dev \
280 vim \
293 vim \
281 virtualenv \
294 virtualenv \
282 wget \
295 wget \
283 xfsprogs \
296 xfsprogs \
284 zip \
297 zip \
285 zlib1g-dev"
298 zlib1g-dev"
286
299
287 if [ "LSB_RELEASE" = "stretch" ]; then
300 if [ "LSB_RELEASE" = "stretch" ]; then
288 PACKAGES="$PACKAGES linux-perf"
301 PACKAGES="$PACKAGES linux-perf"
289 elif [ "$DISTRO" = "Ubuntu" ]; then
302 elif [ "$DISTRO" = "Ubuntu" ]; then
290 PACKAGES="$PACKAGES linux-tools-common"
303 PACKAGES="$PACKAGES linux-tools-common"
291 fi
304 fi
292
305
293 # Monotone only available in older releases.
306 # Monotone only available in older releases.
294 if [ "$LSB_RELEASE" = "stretch" -o "$LSB_RELEASE" = "xenial" ]; then
307 if [ "$LSB_RELEASE" = "stretch" -o "$LSB_RELEASE" = "xenial" ]; then
295 PACKAGES="$PACKAGES monotone"
308 PACKAGES="$PACKAGES monotone"
296 fi
309 fi
297
310
298 sudo DEBIAN_FRONTEND=noninteractive apt-get -yq install --no-install-recommends $PACKAGES
311 sudo DEBIAN_FRONTEND=noninteractive apt-get -yq install --no-install-recommends $PACKAGES
299
312
300 # Create clang-format symlink so test harness finds it.
313 # Create clang-format symlink so test harness finds it.
301 sudo update-alternatives --install /usr/bin/clang-format clang-format \
314 sudo update-alternatives --install /usr/bin/clang-format clang-format \
302 /usr/bin/clang-format-6.0 1000
315 /usr/bin/clang-format-6.0 1000
303
316
304 sudo mkdir /hgdev
317 sudo mkdir /hgdev
305 # Will be normalized to hg:hg later.
318 # Will be normalized to hg:hg later.
306 sudo chown `whoami` /hgdev
319 sudo chown `whoami` /hgdev
307
320
308 {install_rust}
321 {install_rust}
309
322
310 cp requirements-py2.txt /hgdev/requirements-py2.txt
323 cp requirements-*.txt /hgdev/
311 cp requirements-py3.txt /hgdev/requirements-py3.txt
312
324
313 # Disable the pip version check because it uses the network and can
325 # Disable the pip version check because it uses the network and can
314 # be annoying.
326 # be annoying.
315 cat << EOF | sudo tee -a /etc/pip.conf
327 cat << EOF | sudo tee -a /etc/pip.conf
316 [global]
328 [global]
317 disable-pip-version-check = True
329 disable-pip-version-check = True
318 EOF
330 EOF
319
331
320 {install_pythons}
332 {install_pythons}
321 {bootstrap_virtualenv}
333 {bootstrap_virtualenv}
322
334
323 /hgdev/venv-bootstrap/bin/hg clone https://www.mercurial-scm.org/repo/hg /hgdev/src
335 /hgdev/venv-bootstrap/bin/hg clone https://www.mercurial-scm.org/repo/hg /hgdev/src
324
336
325 # Mark the repo as non-publishing.
337 # Mark the repo as non-publishing.
326 cat >> /hgdev/src/.hg/hgrc << EOF
338 cat >> /hgdev/src/.hg/hgrc << EOF
327 [phases]
339 [phases]
328 publish = false
340 publish = false
329 EOF
341 EOF
330
342
331 sudo chown -R hg:hg /hgdev
343 sudo chown -R hg:hg /hgdev
332 '''.lstrip()
344 '''.lstrip()
333 .format(
345 .format(
334 install_rust=INSTALL_RUST,
346 install_rust=INSTALL_RUST,
335 install_pythons=INSTALL_PYTHONS,
347 install_pythons=INSTALL_PYTHONS,
336 bootstrap_virtualenv=BOOTSTRAP_VIRTUALENV,
348 bootstrap_virtualenv=BOOTSTRAP_VIRTUALENV,
337 )
349 )
338 .replace('\r\n', '\n')
350 .replace('\r\n', '\n')
339 )
351 )
340
352
341
353
342 # Prepares /hgdev for operations.
354 # Prepares /hgdev for operations.
343 PREPARE_HGDEV = '''
355 PREPARE_HGDEV = '''
344 #!/bin/bash
356 #!/bin/bash
345
357
346 set -e
358 set -e
347
359
348 FS=$1
360 FS=$1
349
361
350 ensure_device() {
362 ensure_device() {
351 if [ -z "${DEVICE}" ]; then
363 if [ -z "${DEVICE}" ]; then
352 echo "could not find block device to format"
364 echo "could not find block device to format"
353 exit 1
365 exit 1
354 fi
366 fi
355 }
367 }
356
368
357 # Determine device to partition for extra filesystem.
369 # Determine device to partition for extra filesystem.
358 # If only 1 volume is present, it will be the root volume and
370 # If only 1 volume is present, it will be the root volume and
359 # should be /dev/nvme0. If multiple volumes are present, the
371 # should be /dev/nvme0. If multiple volumes are present, the
360 # root volume could be nvme0 or nvme1. Use whichever one doesn't have
372 # root volume could be nvme0 or nvme1. Use whichever one doesn't have
361 # a partition.
373 # a partition.
362 if [ -e /dev/nvme1n1 ]; then
374 if [ -e /dev/nvme1n1 ]; then
363 if [ -e /dev/nvme0n1p1 ]; then
375 if [ -e /dev/nvme0n1p1 ]; then
364 DEVICE=/dev/nvme1n1
376 DEVICE=/dev/nvme1n1
365 else
377 else
366 DEVICE=/dev/nvme0n1
378 DEVICE=/dev/nvme0n1
367 fi
379 fi
368 else
380 else
369 DEVICE=
381 DEVICE=
370 fi
382 fi
371
383
372 sudo mkdir /hgwork
384 sudo mkdir /hgwork
373
385
374 if [ "${FS}" != "default" -a "${FS}" != "tmpfs" ]; then
386 if [ "${FS}" != "default" -a "${FS}" != "tmpfs" ]; then
375 ensure_device
387 ensure_device
376 echo "creating ${FS} filesystem on ${DEVICE}"
388 echo "creating ${FS} filesystem on ${DEVICE}"
377 fi
389 fi
378
390
379 if [ "${FS}" = "default" ]; then
391 if [ "${FS}" = "default" ]; then
380 :
392 :
381
393
382 elif [ "${FS}" = "btrfs" ]; then
394 elif [ "${FS}" = "btrfs" ]; then
383 sudo mkfs.btrfs ${DEVICE}
395 sudo mkfs.btrfs ${DEVICE}
384 sudo mount ${DEVICE} /hgwork
396 sudo mount ${DEVICE} /hgwork
385
397
386 elif [ "${FS}" = "ext3" ]; then
398 elif [ "${FS}" = "ext3" ]; then
387 # lazy_journal_init speeds up filesystem creation at the expense of
399 # lazy_journal_init speeds up filesystem creation at the expense of
388 # integrity if things crash. We are an ephemeral instance, so we don't
400 # integrity if things crash. We are an ephemeral instance, so we don't
389 # care about integrity.
401 # care about integrity.
390 sudo mkfs.ext3 -E lazy_journal_init=1 ${DEVICE}
402 sudo mkfs.ext3 -E lazy_journal_init=1 ${DEVICE}
391 sudo mount ${DEVICE} /hgwork
403 sudo mount ${DEVICE} /hgwork
392
404
393 elif [ "${FS}" = "ext4" ]; then
405 elif [ "${FS}" = "ext4" ]; then
394 sudo mkfs.ext4 -E lazy_journal_init=1 ${DEVICE}
406 sudo mkfs.ext4 -E lazy_journal_init=1 ${DEVICE}
395 sudo mount ${DEVICE} /hgwork
407 sudo mount ${DEVICE} /hgwork
396
408
397 elif [ "${FS}" = "jfs" ]; then
409 elif [ "${FS}" = "jfs" ]; then
398 sudo mkfs.jfs ${DEVICE}
410 sudo mkfs.jfs ${DEVICE}
399 sudo mount ${DEVICE} /hgwork
411 sudo mount ${DEVICE} /hgwork
400
412
401 elif [ "${FS}" = "tmpfs" ]; then
413 elif [ "${FS}" = "tmpfs" ]; then
402 echo "creating tmpfs volume in /hgwork"
414 echo "creating tmpfs volume in /hgwork"
403 sudo mount -t tmpfs -o size=1024M tmpfs /hgwork
415 sudo mount -t tmpfs -o size=1024M tmpfs /hgwork
404
416
405 elif [ "${FS}" = "xfs" ]; then
417 elif [ "${FS}" = "xfs" ]; then
406 sudo mkfs.xfs ${DEVICE}
418 sudo mkfs.xfs ${DEVICE}
407 sudo mount ${DEVICE} /hgwork
419 sudo mount ${DEVICE} /hgwork
408
420
409 else
421 else
410 echo "unsupported filesystem: ${FS}"
422 echo "unsupported filesystem: ${FS}"
411 exit 1
423 exit 1
412 fi
424 fi
413
425
414 echo "/hgwork ready"
426 echo "/hgwork ready"
415
427
416 sudo chown hg:hg /hgwork
428 sudo chown hg:hg /hgwork
417 mkdir /hgwork/tmp
429 mkdir /hgwork/tmp
418 chown hg:hg /hgwork/tmp
430 chown hg:hg /hgwork/tmp
419
431
420 rsync -a /hgdev/src /hgwork/
432 rsync -a /hgdev/src /hgwork/
421 '''.lstrip().replace(
433 '''.lstrip().replace(
422 '\r\n', '\n'
434 '\r\n', '\n'
423 )
435 )
424
436
425
437
426 HG_UPDATE_CLEAN = '''
438 HG_UPDATE_CLEAN = '''
427 set -ex
439 set -ex
428
440
429 HG=/hgdev/venv-bootstrap/bin/hg
441 HG=/hgdev/venv-bootstrap/bin/hg
430
442
431 cd /hgwork/src
443 cd /hgwork/src
432 ${HG} --config extensions.purge= purge --all
444 ${HG} --config extensions.purge= purge --all
433 ${HG} update -C $1
445 ${HG} update -C $1
434 ${HG} log -r .
446 ${HG} log -r .
435 '''.lstrip().replace(
447 '''.lstrip().replace(
436 '\r\n', '\n'
448 '\r\n', '\n'
437 )
449 )
438
450
439
451
440 def prepare_exec_environment(ssh_client, filesystem='default'):
452 def prepare_exec_environment(ssh_client, filesystem='default'):
441 """Prepare an EC2 instance to execute things.
453 """Prepare an EC2 instance to execute things.
442
454
443 The AMI has an ``/hgdev`` bootstrapped with various Python installs
455 The AMI has an ``/hgdev`` bootstrapped with various Python installs
444 and a clone of the Mercurial repo.
456 and a clone of the Mercurial repo.
445
457
446 In EC2, EBS volumes launched from snapshots have wonky performance behavior.
458 In EC2, EBS volumes launched from snapshots have wonky performance behavior.
447 Notably, blocks have to be copied on first access, which makes volume
459 Notably, blocks have to be copied on first access, which makes volume
448 I/O extremely slow on fresh volumes.
460 I/O extremely slow on fresh volumes.
449
461
450 Furthermore, we may want to run operations, tests, etc on alternative
462 Furthermore, we may want to run operations, tests, etc on alternative
451 filesystems so we examine behavior on different filesystems.
463 filesystems so we examine behavior on different filesystems.
452
464
453 This function is used to facilitate executing operations on alternate
465 This function is used to facilitate executing operations on alternate
454 volumes.
466 volumes.
455 """
467 """
456 sftp = ssh_client.open_sftp()
468 sftp = ssh_client.open_sftp()
457
469
458 with sftp.open('/hgdev/prepare-hgdev', 'wb') as fh:
470 with sftp.open('/hgdev/prepare-hgdev', 'wb') as fh:
459 fh.write(PREPARE_HGDEV)
471 fh.write(PREPARE_HGDEV)
460 fh.chmod(0o0777)
472 fh.chmod(0o0777)
461
473
462 command = 'sudo /hgdev/prepare-hgdev %s' % filesystem
474 command = 'sudo /hgdev/prepare-hgdev %s' % filesystem
463 chan, stdin, stdout = exec_command(ssh_client, command)
475 chan, stdin, stdout = exec_command(ssh_client, command)
464 stdin.close()
476 stdin.close()
465
477
466 for line in stdout:
478 for line in stdout:
467 print(line, end='')
479 print(line, end='')
468
480
469 res = chan.recv_exit_status()
481 res = chan.recv_exit_status()
470
482
471 if res:
483 if res:
472 raise Exception('non-0 exit code updating working directory; %d' % res)
484 raise Exception('non-0 exit code updating working directory; %d' % res)
473
485
474
486
475 def synchronize_hg(
487 def synchronize_hg(
476 source_path: pathlib.Path, ec2_instance, revision: str = None
488 source_path: pathlib.Path, ec2_instance, revision: str = None
477 ):
489 ):
478 """Synchronize a local Mercurial source path to remote EC2 instance."""
490 """Synchronize a local Mercurial source path to remote EC2 instance."""
479
491
480 with tempfile.TemporaryDirectory() as temp_dir:
492 with tempfile.TemporaryDirectory() as temp_dir:
481 temp_dir = pathlib.Path(temp_dir)
493 temp_dir = pathlib.Path(temp_dir)
482
494
483 ssh_dir = temp_dir / '.ssh'
495 ssh_dir = temp_dir / '.ssh'
484 ssh_dir.mkdir()
496 ssh_dir.mkdir()
485 ssh_dir.chmod(0o0700)
497 ssh_dir.chmod(0o0700)
486
498
487 public_ip = ec2_instance.public_ip_address
499 public_ip = ec2_instance.public_ip_address
488
500
489 ssh_config = ssh_dir / 'config'
501 ssh_config = ssh_dir / 'config'
490
502
491 with ssh_config.open('w', encoding='utf-8') as fh:
503 with ssh_config.open('w', encoding='utf-8') as fh:
492 fh.write('Host %s\n' % public_ip)
504 fh.write('Host %s\n' % public_ip)
493 fh.write(' User hg\n')
505 fh.write(' User hg\n')
494 fh.write(' StrictHostKeyChecking no\n')
506 fh.write(' StrictHostKeyChecking no\n')
495 fh.write(' UserKnownHostsFile %s\n' % (ssh_dir / 'known_hosts'))
507 fh.write(' UserKnownHostsFile %s\n' % (ssh_dir / 'known_hosts'))
496 fh.write(' IdentityFile %s\n' % ec2_instance.ssh_private_key_path)
508 fh.write(' IdentityFile %s\n' % ec2_instance.ssh_private_key_path)
497
509
498 if not (source_path / '.hg').is_dir():
510 if not (source_path / '.hg').is_dir():
499 raise Exception(
511 raise Exception(
500 '%s is not a Mercurial repository; synchronization '
512 '%s is not a Mercurial repository; synchronization '
501 'not yet supported' % source_path
513 'not yet supported' % source_path
502 )
514 )
503
515
504 env = dict(os.environ)
516 env = dict(os.environ)
505 env['HGPLAIN'] = '1'
517 env['HGPLAIN'] = '1'
506 env['HGENCODING'] = 'utf-8'
518 env['HGENCODING'] = 'utf-8'
507
519
508 hg_bin = source_path / 'hg'
520 hg_bin = source_path / 'hg'
509
521
510 res = subprocess.run(
522 res = subprocess.run(
511 ['python2.7', str(hg_bin), 'log', '-r', revision, '-T', '{node}'],
523 ['python2.7', str(hg_bin), 'log', '-r', revision, '-T', '{node}'],
512 cwd=str(source_path),
524 cwd=str(source_path),
513 env=env,
525 env=env,
514 check=True,
526 check=True,
515 capture_output=True,
527 capture_output=True,
516 )
528 )
517
529
518 full_revision = res.stdout.decode('ascii')
530 full_revision = res.stdout.decode('ascii')
519
531
520 args = [
532 args = [
521 'python2.7',
533 'python2.7',
522 str(hg_bin),
534 str(hg_bin),
523 '--config',
535 '--config',
524 'ui.ssh=ssh -F %s' % ssh_config,
536 'ui.ssh=ssh -F %s' % ssh_config,
525 '--config',
537 '--config',
526 'ui.remotecmd=/hgdev/venv-bootstrap/bin/hg',
538 'ui.remotecmd=/hgdev/venv-bootstrap/bin/hg',
527 # Also ensure .hgtags changes are present so auto version
539 # Also ensure .hgtags changes are present so auto version
528 # calculation works.
540 # calculation works.
529 'push',
541 'push',
530 '-f',
542 '-f',
531 '-r',
543 '-r',
532 full_revision,
544 full_revision,
533 '-r',
545 '-r',
534 'file(.hgtags)',
546 'file(.hgtags)',
535 'ssh://%s//hgwork/src' % public_ip,
547 'ssh://%s//hgwork/src' % public_ip,
536 ]
548 ]
537
549
538 res = subprocess.run(args, cwd=str(source_path), env=env)
550 res = subprocess.run(args, cwd=str(source_path), env=env)
539
551
540 # Allow 1 (no-op) to not trigger error.
552 # Allow 1 (no-op) to not trigger error.
541 if res.returncode not in (0, 1):
553 if res.returncode not in (0, 1):
542 res.check_returncode()
554 res.check_returncode()
543
555
544 # TODO support synchronizing dirty working directory.
556 # TODO support synchronizing dirty working directory.
545
557
546 sftp = ec2_instance.ssh_client.open_sftp()
558 sftp = ec2_instance.ssh_client.open_sftp()
547
559
548 with sftp.open('/hgdev/hgup', 'wb') as fh:
560 with sftp.open('/hgdev/hgup', 'wb') as fh:
549 fh.write(HG_UPDATE_CLEAN)
561 fh.write(HG_UPDATE_CLEAN)
550 fh.chmod(0o0700)
562 fh.chmod(0o0700)
551
563
552 chan, stdin, stdout = exec_command(
564 chan, stdin, stdout = exec_command(
553 ec2_instance.ssh_client, '/hgdev/hgup %s' % full_revision
565 ec2_instance.ssh_client, '/hgdev/hgup %s' % full_revision
554 )
566 )
555 stdin.close()
567 stdin.close()
556
568
557 for line in stdout:
569 for line in stdout:
558 print(line, end='')
570 print(line, end='')
559
571
560 res = chan.recv_exit_status()
572 res = chan.recv_exit_status()
561
573
562 if res:
574 if res:
563 raise Exception(
575 raise Exception(
564 'non-0 exit code updating working directory; %d' % res
576 'non-0 exit code updating working directory; %d' % res
565 )
577 )
566
578
567
579
568 def run_tests(ssh_client, python_version, test_flags=None):
580 def run_tests(ssh_client, python_version, test_flags=None):
569 """Run tests on a remote Linux machine via an SSH client."""
581 """Run tests on a remote Linux machine via an SSH client."""
570 test_flags = test_flags or []
582 test_flags = test_flags or []
571
583
572 print('running tests')
584 print('running tests')
573
585
574 if python_version == 'system2':
586 if python_version == 'system2':
575 python = '/usr/bin/python2'
587 python = '/usr/bin/python2'
576 elif python_version == 'system3':
588 elif python_version == 'system3':
577 python = '/usr/bin/python3'
589 python = '/usr/bin/python3'
578 elif python_version.startswith('pypy'):
590 elif python_version.startswith('pypy'):
579 python = '/hgdev/pyenv/shims/%s' % python_version
591 python = '/hgdev/pyenv/shims/%s' % python_version
580 else:
592 else:
581 python = '/hgdev/pyenv/shims/python%s' % python_version
593 python = '/hgdev/pyenv/shims/python%s' % python_version
582
594
583 test_flags = ' '.join(shlex.quote(a) for a in test_flags)
595 test_flags = ' '.join(shlex.quote(a) for a in test_flags)
584
596
585 command = (
597 command = (
586 '/bin/sh -c "export TMPDIR=/hgwork/tmp; '
598 '/bin/sh -c "export TMPDIR=/hgwork/tmp; '
587 'cd /hgwork/src/tests && %s run-tests.py %s"' % (python, test_flags)
599 'cd /hgwork/src/tests && %s run-tests.py %s"' % (python, test_flags)
588 )
600 )
589
601
590 chan, stdin, stdout = exec_command(ssh_client, command)
602 chan, stdin, stdout = exec_command(ssh_client, command)
591
603
592 stdin.close()
604 stdin.close()
593
605
594 for line in stdout:
606 for line in stdout:
595 print(line, end='')
607 print(line, end='')
596
608
597 return chan.recv_exit_status()
609 return chan.recv_exit_status()
@@ -1,215 +1,306
1 #
1 #
2 # This file is autogenerated by pip-compile
2 # This file is autogenerated by pip-compile
3 # To update, run:
3 # To update, run:
4 #
4 #
5 # pip-compile --generate-hashes --output-file=contrib/automation/linux-requirements-py3.txt contrib/automation/linux-requirements.txt.in
5 # pip-compile --generate-hashes --output-file=contrib/automation/linux-requirements-py3.txt contrib/automation/linux-requirements.txt.in
6 #
6 #
7 appdirs==1.4.4 \
7 appdirs==1.4.4 \
8 --hash=sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41 \
8 --hash=sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41 \
9 --hash=sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128 \
9 --hash=sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128
10 # via black
10 # via black
11 astroid==2.4.2 \
11 astroid==2.5.6 \
12 --hash=sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703 \
12 --hash=sha256:4db03ab5fc3340cf619dbc25e42c2cc3755154ce6009469766d7143d1fc2ee4e \
13 --hash=sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386 \
13 --hash=sha256:8a398dfce302c13f14bab13e2b14fe385d32b73f4e4853b9bdfb64598baa1975
14 # via pylint
14 # via pylint
15 attrs==20.2.0 \
15 attrs==21.1.0 \
16 --hash=sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594 \
16 --hash=sha256:3901be1cb7c2a780f14668691474d9252c070a756be0a9ead98cfeabfa11aeb8 \
17 --hash=sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc \
17 --hash=sha256:8ee1e5f5a1afc5b19bdfae4fdf0c35ed324074bdce3500c939842c8f818645d9
18 # via black
18 # via black
19 black==19.10b0 ; python_version >= "3.6" and platform_python_implementation != "PyPy" \
19 black==19.10b0 ; python_version >= "3.6" and platform_python_implementation != "PyPy" \
20 --hash=sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b \
20 --hash=sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b \
21 --hash=sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539 \
21 --hash=sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539
22 # via -r contrib/automation/linux-requirements.txt.in
22 # via -r contrib/automation/linux-requirements.txt.in
23 click==7.1.2 \
23 click==7.1.2 \
24 --hash=sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a \
24 --hash=sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a \
25 --hash=sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc \
25 --hash=sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc
26 # via black
26 # via black
27 docutils==0.16 \
27 docutils==0.17.1 \
28 --hash=sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af \
28 --hash=sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125 \
29 --hash=sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc \
29 --hash=sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61
30 # via -r contrib/automation/linux-requirements.txt.in
30 # via -r contrib/automation/linux-requirements.txt.in
31 fuzzywuzzy==0.18.0 \
31 fuzzywuzzy==0.18.0 \
32 --hash=sha256:45016e92264780e58972dca1b3d939ac864b78437422beecebb3095f8efd00e8 \
32 --hash=sha256:45016e92264780e58972dca1b3d939ac864b78437422beecebb3095f8efd00e8 \
33 --hash=sha256:928244b28db720d1e0ee7587acf660ea49d7e4c632569cad4f1cd7e68a5f0993 \
33 --hash=sha256:928244b28db720d1e0ee7587acf660ea49d7e4c632569cad4f1cd7e68a5f0993
34 # via -r contrib/automation/linux-requirements.txt.in
34 # via -r contrib/automation/linux-requirements.txt.in
35 idna==2.10 \
35 idna==3.1 \
36 --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \
36 --hash=sha256:5205d03e7bcbb919cc9c19885f9920d622ca52448306f2377daede5cf3faac16 \
37 --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 \
37 --hash=sha256:c5b02147e01ea9920e6b0a3f1f7bb833612d507592c837a6c49552768f4054e1
38 # via yarl
38 # via yarl
39 isort==4.3.21 \
39 isort==4.3.21 \
40 --hash=sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1 \
40 --hash=sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1 \
41 --hash=sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd \
41 --hash=sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd
42 # via -r contrib/automation/linux-requirements.txt.in, pylint
42 # via
43 lazy-object-proxy==1.4.3 \
43 # -r contrib/automation/linux-requirements.txt.in
44 --hash=sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d \
44 # pylint
45 --hash=sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449 \
45 lazy-object-proxy==1.6.0 \
46 --hash=sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08 \
46 --hash=sha256:17e0967ba374fc24141738c69736da90e94419338fd4c7c7bef01ee26b339653 \
47 --hash=sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a \
47 --hash=sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61 \
48 --hash=sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50 \
48 --hash=sha256:22ddd618cefe54305df49e4c069fa65715be4ad0e78e8d252a33debf00f6ede2 \
49 --hash=sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd \
49 --hash=sha256:24a5045889cc2729033b3e604d496c2b6f588c754f7a62027ad4437a7ecc4837 \
50 --hash=sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239 \
50 --hash=sha256:410283732af311b51b837894fa2f24f2c0039aa7f220135192b38fcc42bd43d3 \
51 --hash=sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb \
51 --hash=sha256:4732c765372bd78a2d6b2150a6e99d00a78ec963375f236979c0626b97ed8e43 \
52 --hash=sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea \
52 --hash=sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726 \
53 --hash=sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e \
53 --hash=sha256:4f60460e9f1eb632584c9685bccea152f4ac2130e299784dbaf9fae9f49891b3 \
54 --hash=sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156 \
54 --hash=sha256:5743a5ab42ae40caa8421b320ebf3a998f89c85cdc8376d6b2e00bd12bd1b587 \
55 --hash=sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142 \
55 --hash=sha256:85fb7608121fd5621cc4377a8961d0b32ccf84a7285b4f1d21988b2eae2868e8 \
56 --hash=sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442 \
56 --hash=sha256:9698110e36e2df951c7c36b6729e96429c9c32b3331989ef19976592c5f3c77a \
57 --hash=sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62 \
57 --hash=sha256:9d397bf41caad3f489e10774667310d73cb9c4258e9aed94b9ec734b34b495fd \
58 --hash=sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db \
58 --hash=sha256:b579f8acbf2bdd9ea200b1d5dea36abd93cabf56cf626ab9c744a432e15c815f \
59 --hash=sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531 \
59 --hash=sha256:b865b01a2e7f96db0c5d12cfea590f98d8c5ba64ad222300d93ce6ff9138bcad \
60 --hash=sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383 \
60 --hash=sha256:bf34e368e8dd976423396555078def5cfc3039ebc6fc06d1ae2c5a65eebbcde4 \
61 --hash=sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a \
61 --hash=sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b \
62 --hash=sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357 \
62 --hash=sha256:d1c2676e3d840852a2de7c7d5d76407c772927addff8d742b9808fe0afccebdf \
63 --hash=sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4 \
63 --hash=sha256:d7124f52f3bd259f510651450e18e0fd081ed82f3c08541dffc7b94b883aa981 \
64 --hash=sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0 \
64 --hash=sha256:d900d949b707778696fdf01036f58c9876a0d8bfe116e8d220cfd4b15f14e741 \
65 --hash=sha256:ebfd274dcd5133e0afae738e6d9da4323c3eb021b3e13052d8cbd0e457b1256e \
66 --hash=sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93 \
67 --hash=sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b
65 # via astroid
68 # via astroid
66 mccabe==0.6.1 \
69 mccabe==0.6.1 \
67 --hash=sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42 \
70 --hash=sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42 \
68 --hash=sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f \
71 --hash=sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f
69 # via pylint
72 # via pylint
70 multidict==4.7.6 \
73 multidict==5.1.0 \
71 --hash=sha256:1ece5a3369835c20ed57adadc663400b5525904e53bae59ec854a5d36b39b21a \
74 --hash=sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a \
72 --hash=sha256:275ca32383bc5d1894b6975bb4ca6a7ff16ab76fa622967625baeebcf8079000 \
75 --hash=sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93 \
73 --hash=sha256:3750f2205b800aac4bb03b5ae48025a64e474d2c6cc79547988ba1d4122a09e2 \
76 --hash=sha256:05c20b68e512166fddba59a918773ba002fdd77800cad9f55b59790030bab632 \
74 --hash=sha256:4538273208e7294b2659b1602490f4ed3ab1c8cf9dbdd817e0e9db8e64be2507 \
77 --hash=sha256:07b42215124aedecc6083f1ce6b7e5ec5b50047afa701f3442054373a6deb656 \
75 --hash=sha256:5141c13374e6b25fe6bf092052ab55c0c03d21bd66c94a0e3ae371d3e4d865a5 \
78 --hash=sha256:0e3c84e6c67eba89c2dbcee08504ba8644ab4284863452450520dad8f1e89b79 \
76 --hash=sha256:51a4d210404ac61d32dada00a50ea7ba412e6ea945bbe992e4d7a595276d2ec7 \
79 --hash=sha256:0e929169f9c090dae0646a011c8b058e5e5fb391466016b39d21745b48817fd7 \
77 --hash=sha256:5cf311a0f5ef80fe73e4f4c0f0998ec08f954a6ec72b746f3c179e37de1d210d \
80 --hash=sha256:1ab820665e67373de5802acae069a6a05567ae234ddb129f31d290fc3d1aa56d \
78 --hash=sha256:6513728873f4326999429a8b00fc7ceddb2509b01d5fd3f3be7881a257b8d463 \
81 --hash=sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5 \
79 --hash=sha256:7388d2ef3c55a8ba80da62ecfafa06a1c097c18032a501ffd4cabbc52d7f2b19 \
82 --hash=sha256:2e68965192c4ea61fff1b81c14ff712fc7dc15d2bd120602e4a3494ea6584224 \
80 --hash=sha256:9456e90649005ad40558f4cf51dbb842e32807df75146c6d940b6f5abb4a78f3 \
83 --hash=sha256:2f1a132f1c88724674271d636e6b7351477c27722f2ed789f719f9e3545a3d26 \
81 --hash=sha256:c026fe9a05130e44157b98fea3ab12969e5b60691a276150db9eda71710cd10b \
84 --hash=sha256:37e5438e1c78931df5d3c0c78ae049092877e5e9c02dd1ff5abb9cf27a5914ea \
82 --hash=sha256:d14842362ed4cf63751648e7672f7174c9818459d169231d03c56e84daf90b7c \
85 --hash=sha256:3a041b76d13706b7fff23b9fc83117c7b8fe8d5fe9e6be45eee72b9baa75f348 \
83 --hash=sha256:e0d072ae0f2a179c375f67e3da300b47e1a83293c554450b29c900e50afaae87 \
86 --hash=sha256:3a4f32116f8f72ecf2a29dabfb27b23ab7cdc0ba807e8459e59a93a9be9506f6 \
84 --hash=sha256:f07acae137b71af3bb548bd8da720956a3bc9f9a0b87733e0899226a2317aeb7 \
87 --hash=sha256:46c73e09ad374a6d876c599f2328161bcd95e280f84d2060cf57991dec5cfe76 \
85 --hash=sha256:fbb77a75e529021e7c4a8d4e823d88ef4d23674a202be4f5addffc72cbb91430 \
88 --hash=sha256:46dd362c2f045095c920162e9307de5ffd0a1bfbba0a6e990b344366f55a30c1 \
86 --hash=sha256:fcfbb44c59af3f8ea984de67ec7c306f618a3ec771c2843804069917a8f2e255 \
89 --hash=sha256:4b186eb7d6ae7c06eb4392411189469e6a820da81447f46c0072a41c748ab73f \
87 --hash=sha256:feed85993dbdb1dbc29102f50bca65bdc68f2c0c8d352468c25b54874f23c39d \
90 --hash=sha256:54fd1e83a184e19c598d5e70ba508196fd0bbdd676ce159feb412a4a6664f952 \
91 --hash=sha256:585fd452dd7782130d112f7ddf3473ffdd521414674c33876187e101b588738a \
92 --hash=sha256:5cf3443199b83ed9e955f511b5b241fd3ae004e3cb81c58ec10f4fe47c7dce37 \
93 --hash=sha256:6a4d5ce640e37b0efcc8441caeea8f43a06addace2335bd11151bc02d2ee31f9 \
94 --hash=sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359 \
95 --hash=sha256:806068d4f86cb06af37cd65821554f98240a19ce646d3cd24e1c33587f313eb8 \
96 --hash=sha256:830f57206cc96ed0ccf68304141fec9481a096c4d2e2831f311bde1c404401da \
97 --hash=sha256:929006d3c2d923788ba153ad0de8ed2e5ed39fdbe8e7be21e2f22ed06c6783d3 \
98 --hash=sha256:9436dc58c123f07b230383083855593550c4d301d2532045a17ccf6eca505f6d \
99 --hash=sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf \
100 --hash=sha256:ace010325c787c378afd7f7c1ac66b26313b3344628652eacd149bdd23c68841 \
101 --hash=sha256:b47a43177a5e65b771b80db71e7be76c0ba23cc8aa73eeeb089ed5219cdbe27d \
102 --hash=sha256:b797515be8743b771aa868f83563f789bbd4b236659ba52243b735d80b29ed93 \
103 --hash=sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f \
104 --hash=sha256:d5c65bdf4484872c4af3150aeebe101ba560dcfb34488d9a8ff8dbcd21079647 \
105 --hash=sha256:d81eddcb12d608cc08081fa88d046c78afb1bf8107e6feab5d43503fea74a635 \
106 --hash=sha256:dc862056f76443a0db4509116c5cd480fe1b6a2d45512a653f9a855cc0517456 \
107 --hash=sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda \
108 --hash=sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5 \
109 --hash=sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281 \
110 --hash=sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80
88 # via yarl
111 # via yarl
89 pathspec==0.8.0 \
112 pathspec==0.8.1 \
90 --hash=sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0 \
113 --hash=sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd \
91 --hash=sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061 \
114 --hash=sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d
92 # via black
115 # via black
93 pyflakes==2.2.0 \
116 pyflakes==2.3.1 \
94 --hash=sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92 \
117 --hash=sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3 \
95 --hash=sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8 \
118 --hash=sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db
96 # via -r contrib/automation/linux-requirements.txt.in
119 # via -r contrib/automation/linux-requirements.txt.in
97 pygments==2.7.1 \
120 pygments==2.9.0 \
98 --hash=sha256:307543fe65c0947b126e83dd5a61bd8acbd84abec11f43caebaf5534cbc17998 \
121 --hash=sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f \
99 --hash=sha256:926c3f319eda178d1bd90851e4317e6d8cdb5e292a3386aac9bd75eca29cf9c7 \
122 --hash=sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e
100 # via -r contrib/automation/linux-requirements.txt.in
123 # via -r contrib/automation/linux-requirements.txt.in
101 pylint==2.6.0 \
124 pylint==2.8.2 \
102 --hash=sha256:bb4a908c9dadbc3aac18860550e870f58e1a02c9f2c204fdf5693d73be061210 \
125 --hash=sha256:586d8fa9b1891f4b725f587ef267abe2a1bad89d6b184520c7f07a253dd6e217 \
103 --hash=sha256:bfe68f020f8a0fece830a22dd4d5dddb4ecc6137db04face4c3420a46a52239f \
126 --hash=sha256:f7e2072654a6b6afdf5e2fb38147d3e2d2d43c89f648637baab63e026481279b
127 # via -r contrib/automation/linux-requirements.txt.in
128 python-levenshtein==0.12.2 \
129 --hash=sha256:dc2395fbd148a1ab31090dd113c366695934b9e85fe5a4b2a032745efd0346f6
104 # via -r contrib/automation/linux-requirements.txt.in
130 # via -r contrib/automation/linux-requirements.txt.in
105 python-levenshtein==0.12.0 \
131 pyyaml==5.4.1 \
106 --hash=sha256:033a11de5e3d19ea25c9302d11224e1a1898fe5abd23c61c7c360c25195e3eb1 \
132 --hash=sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf \
107 # via -r contrib/automation/linux-requirements.txt.in
133 --hash=sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696 \
108 pyyaml==5.3.1 \
134 --hash=sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393 \
109 --hash=sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97 \
135 --hash=sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77 \
110 --hash=sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76 \
136 --hash=sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922 \
111 --hash=sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2 \
137 --hash=sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5 \
112 --hash=sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648 \
138 --hash=sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8 \
113 --hash=sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf \
139 --hash=sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10 \
114 --hash=sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f \
140 --hash=sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc \
115 --hash=sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2 \
141 --hash=sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018 \
116 --hash=sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee \
142 --hash=sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e \
117 --hash=sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d \
143 --hash=sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253 \
118 --hash=sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c \
144 --hash=sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347 \
119 --hash=sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a \
145 --hash=sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183 \
146 --hash=sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541 \
147 --hash=sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb \
148 --hash=sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185 \
149 --hash=sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc \
150 --hash=sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db \
151 --hash=sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa \
152 --hash=sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46 \
153 --hash=sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122 \
154 --hash=sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b \
155 --hash=sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63 \
156 --hash=sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df \
157 --hash=sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc \
158 --hash=sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247 \
159 --hash=sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6 \
160 --hash=sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0
120 # via vcrpy
161 # via vcrpy
121 regex==2020.9.27 \
162 regex==2021.4.4 \
122 --hash=sha256:088afc8c63e7bd187a3c70a94b9e50ab3f17e1d3f52a32750b5b77dbe99ef5ef \
163 --hash=sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5 \
123 --hash=sha256:1fe0a41437bbd06063aa184c34804efa886bcc128222e9916310c92cd54c3b4c \
164 --hash=sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79 \
124 --hash=sha256:3d20024a70b97b4f9546696cbf2fd30bae5f42229fbddf8661261b1eaff0deb7 \
165 --hash=sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31 \
125 --hash=sha256:41bb65f54bba392643557e617316d0d899ed5b4946dccee1cb6696152b29844b \
166 --hash=sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500 \
126 --hash=sha256:4318d56bccfe7d43e5addb272406ade7a2274da4b70eb15922a071c58ab0108c \
167 --hash=sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11 \
127 --hash=sha256:4707f3695b34335afdfb09be3802c87fa0bc27030471dbc082f815f23688bc63 \
168 --hash=sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14 \
128 --hash=sha256:49f23ebd5ac073765ecbcf046edc10d63dcab2f4ae2bce160982cb30df0c0302 \
169 --hash=sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3 \
129 --hash=sha256:5533a959a1748a5c042a6da71fe9267a908e21eded7a4f373efd23a2cbdb0ecc \
170 --hash=sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439 \
130 --hash=sha256:5d892a4f1c999834eaa3c32bc9e8b976c5825116cde553928c4c8e7e48ebda67 \
171 --hash=sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c \
131 --hash=sha256:5f18875ac23d9aa2f060838e8b79093e8bb2313dbaaa9f54c6d8e52a5df097be \
172 --hash=sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82 \
132 --hash=sha256:60b0e9e6dc45683e569ec37c55ac20c582973841927a85f2d8a7d20ee80216ab \
173 --hash=sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711 \
133 --hash=sha256:816064fc915796ea1f26966163f6845de5af78923dfcecf6551e095f00983650 \
174 --hash=sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093 \
134 --hash=sha256:84cada8effefe9a9f53f9b0d2ba9b7b6f5edf8d2155f9fdbe34616e06ececf81 \
175 --hash=sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a \
135 --hash=sha256:84e9407db1b2eb368b7ecc283121b5e592c9aaedbe8c78b1a2f1102eb2e21d19 \
176 --hash=sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb \
136 --hash=sha256:8d69cef61fa50c8133382e61fd97439de1ae623fe943578e477e76a9d9471637 \
177 --hash=sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8 \
137 --hash=sha256:9a02d0ae31d35e1ec12a4ea4d4cca990800f66a917d0fb997b20fbc13f5321fc \
178 --hash=sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17 \
138 --hash=sha256:9bc13e0d20b97ffb07821aa3e113f9998e84994fe4d159ffa3d3a9d1b805043b \
179 --hash=sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000 \
139 --hash=sha256:a6f32aea4260dfe0e55dc9733ea162ea38f0ea86aa7d0f77b15beac5bf7b369d \
180 --hash=sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d \
140 --hash=sha256:ae91972f8ac958039920ef6e8769277c084971a142ce2b660691793ae44aae6b \
181 --hash=sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480 \
141 --hash=sha256:c570f6fa14b9c4c8a4924aaad354652366577b4f98213cf76305067144f7b100 \
182 --hash=sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc \
142 --hash=sha256:c9443124c67b1515e4fe0bb0aa18df640965e1030f468a2a5dc2589b26d130ad \
183 --hash=sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0 \
143 --hash=sha256:d23a18037313714fb3bb5a94434d3151ee4300bae631894b1ac08111abeaa4a3 \
184 --hash=sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9 \
144 --hash=sha256:eaf548d117b6737df379fdd53bdde4f08870e66d7ea653e230477f071f861121 \
185 --hash=sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765 \
145 --hash=sha256:ebbe29186a3d9b0c591e71b7393f1ae08c83cb2d8e517d2a822b8f7ec99dfd8b \
186 --hash=sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e \
146 --hash=sha256:eda4771e0ace7f67f58bc5b560e27fb20f32a148cbc993b0c3835970935c2707 \
187 --hash=sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a \
147 --hash=sha256:f1b3afc574a3db3b25c89161059d857bd4909a1269b0b3cb3c904677c8c4a3f7 \
188 --hash=sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07 \
148 --hash=sha256:f2388013e68e750eaa16ccbea62d4130180c26abb1d8e5d584b9baf69672b30f \
189 --hash=sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f \
190 --hash=sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac \
191 --hash=sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7 \
192 --hash=sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed \
193 --hash=sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968 \
194 --hash=sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7 \
195 --hash=sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2 \
196 --hash=sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4 \
197 --hash=sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87 \
198 --hash=sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8 \
199 --hash=sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10 \
200 --hash=sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29 \
201 --hash=sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605 \
202 --hash=sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6 \
203 --hash=sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042
149 # via black
204 # via black
150 six==1.15.0 \
205 six==1.16.0 \
151 --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \
206 --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
152 --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced \
207 --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
153 # via astroid, vcrpy
208 # via vcrpy
154 toml==0.10.1 \
209 toml==0.10.2 \
155 --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \
210 --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \
156 --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 \
211 --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f
157 # via black, pylint
212 # via
158 typed-ast==1.4.1 ; python_version >= "3.0" and platform_python_implementation != "PyPy" \
213 # black
159 --hash=sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355 \
214 # pylint
160 --hash=sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919 \
215 typed-ast==1.4.3 ; python_version >= "3.0" and platform_python_implementation != "PyPy" \
161 --hash=sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa \
216 --hash=sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace \
162 --hash=sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652 \
217 --hash=sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff \
163 --hash=sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75 \
218 --hash=sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266 \
164 --hash=sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01 \
219 --hash=sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528 \
165 --hash=sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d \
220 --hash=sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6 \
166 --hash=sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1 \
221 --hash=sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808 \
167 --hash=sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907 \
222 --hash=sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4 \
168 --hash=sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c \
223 --hash=sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363 \
169 --hash=sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3 \
224 --hash=sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341 \
170 --hash=sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b \
225 --hash=sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04 \
171 --hash=sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614 \
226 --hash=sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41 \
172 --hash=sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb \
227 --hash=sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e \
173 --hash=sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b \
228 --hash=sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3 \
174 --hash=sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41 \
229 --hash=sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899 \
175 --hash=sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6 \
230 --hash=sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805 \
176 --hash=sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34 \
231 --hash=sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c \
177 --hash=sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe \
232 --hash=sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c \
178 --hash=sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4 \
233 --hash=sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39 \
179 --hash=sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7 \
234 --hash=sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a \
180 # via -r contrib/automation/linux-requirements.txt.in, astroid, black
235 --hash=sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3 \
181 typing-extensions==3.7.4.3 \
236 --hash=sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7 \
182 --hash=sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918 \
237 --hash=sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f \
183 --hash=sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c \
238 --hash=sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075 \
184 --hash=sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f \
239 --hash=sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0 \
240 --hash=sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40 \
241 --hash=sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428 \
242 --hash=sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927 \
243 --hash=sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3 \
244 --hash=sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f \
245 --hash=sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65
246 # via
247 # -r contrib/automation/linux-requirements.txt.in
248 # astroid
249 # black
250 typing-extensions==3.10.0.0 \
251 --hash=sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497 \
252 --hash=sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342 \
253 --hash=sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84
185 # via yarl
254 # via yarl
186 vcrpy==4.1.0 \
255 vcrpy==4.1.1 \
187 --hash=sha256:4138e79eb35981ad391406cbb7227bce7eba8bad788dcf1a89c2e4a8b740debe \
256 --hash=sha256:12c3fcdae7b88ecf11fc0d3e6d77586549d4575a2ceee18e82eee75c1f626162 \
188 --hash=sha256:d833248442bbc560599add895c9ab0ef518676579e8dc72d8b0933bdb3880253 \
257 --hash=sha256:57095bf22fc0a2d99ee9674cdafebed0f3ba763018582450706f7d3a74fff599
189 # via -r contrib/automation/linux-requirements.txt.in
258 # via -r contrib/automation/linux-requirements.txt.in
190 wrapt==1.12.1 \
259 wrapt==1.12.1 \
191 --hash=sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7 \
260 --hash=sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7
192 # via astroid, vcrpy
261 # via
193 yarl==1.6.0 \
262 # astroid
194 --hash=sha256:04a54f126a0732af75e5edc9addeaa2113e2ca7c6fce8974a63549a70a25e50e \
263 # vcrpy
195 --hash=sha256:3cc860d72ed989f3b1f3abbd6ecf38e412de722fb38b8f1b1a086315cf0d69c5 \
264 yarl==1.6.3 \
196 --hash=sha256:5d84cc36981eb5a8533be79d6c43454c8e6a39ee3118ceaadbd3c029ab2ee580 \
265 --hash=sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e \
197 --hash=sha256:5e447e7f3780f44f890360ea973418025e8c0cdcd7d6a1b221d952600fd945dc \
266 --hash=sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434 \
198 --hash=sha256:61d3ea3c175fe45f1498af868879c6ffeb989d4143ac542163c45538ba5ec21b \
267 --hash=sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366 \
199 --hash=sha256:67c5ea0970da882eaf9efcf65b66792557c526f8e55f752194eff8ec722c75c2 \
268 --hash=sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3 \
200 --hash=sha256:6f6898429ec3c4cfbef12907047136fd7b9e81a6ee9f105b45505e633427330a \
269 --hash=sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec \
201 --hash=sha256:7ce35944e8e61927a8f4eb78f5bc5d1e6da6d40eadd77e3f79d4e9399e263921 \
270 --hash=sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959 \
202 --hash=sha256:b7c199d2cbaf892ba0f91ed36d12ff41ecd0dde46cbf64ff4bfe997a3ebc925e \
271 --hash=sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e \
203 --hash=sha256:c15d71a640fb1f8e98a1423f9c64d7f1f6a3a168f803042eaf3a5b5022fde0c1 \
272 --hash=sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c \
204 --hash=sha256:c22607421f49c0cb6ff3ed593a49b6a99c6ffdeaaa6c944cdda83c2393c8864d \
273 --hash=sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6 \
205 --hash=sha256:c604998ab8115db802cc55cb1b91619b2831a6128a62ca7eea577fc8ea4d3131 \
274 --hash=sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a \
206 --hash=sha256:d088ea9319e49273f25b1c96a3763bf19a882cff774d1792ae6fba34bd40550a \
275 --hash=sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6 \
207 --hash=sha256:db9eb8307219d7e09b33bcb43287222ef35cbcf1586ba9472b0a4b833666ada1 \
276 --hash=sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424 \
208 --hash=sha256:e31fef4e7b68184545c3d68baec7074532e077bd1906b040ecfba659737df188 \
277 --hash=sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e \
209 --hash=sha256:e32f0fb443afcfe7f01f95172b66f279938fbc6bdaebe294b0ff6747fb6db020 \
278 --hash=sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f \
210 --hash=sha256:fcbe419805c9b20db9a51d33b942feddbf6e7fb468cb20686fd7089d4164c12a \
279 --hash=sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50 \
280 --hash=sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2 \
281 --hash=sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc \
282 --hash=sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4 \
283 --hash=sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970 \
284 --hash=sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10 \
285 --hash=sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0 \
286 --hash=sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406 \
287 --hash=sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896 \
288 --hash=sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643 \
289 --hash=sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721 \
290 --hash=sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478 \
291 --hash=sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724 \
292 --hash=sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e \
293 --hash=sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8 \
294 --hash=sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96 \
295 --hash=sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25 \
296 --hash=sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76 \
297 --hash=sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2 \
298 --hash=sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2 \
299 --hash=sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c \
300 --hash=sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a \
301 --hash=sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71
211 # via vcrpy
302 # via vcrpy
212
303
213 # WARNING: The following packages were not pinned, but pip requires them to be
304 # WARNING: The following packages were not pinned, but pip requires them to be
214 # pinned when the requirements file includes hashes. Consider using the --allow-unsafe flag.
305 # pinned when the requirements file includes hashes. Consider using the --allow-unsafe flag.
215 # setuptools
306 # setuptools
General Comments 0
You need to be logged in to leave comments. Login now