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