77from urllib .parse import urlparse
88from urllib .request import urlopen , Request
99
10- UPLOAD_URL_PREFIX = os .getenv ("UPLOAD_URL_PREFIX" ) or "https://www.python.org/ftp/"
11- UPLOAD_PATH_PREFIX = os .getenv ("UPLOAD_PATH_PREFIX" ) or "/srv/www.python.org/ftp/"
12- INDEX_URL = os .getenv ("INDEX_URL" ) or f"https://www.python.org/ftp/python/__index_windows__.json"
13- UPLOAD_HOST = os .getenv ("PyDotOrgServer" )
14- UPLOAD_HOST_KEY = os .getenv ("PyDotOrgHostKey" )
10+ UPLOAD_URL_PREFIX = os .getenv ("UPLOAD_URL_PREFIX" )
11+ UPLOAD_PATH_PREFIX = os .getenv ("UPLOAD_PATH_PREFIX" )
12+ INDEX_URL = os .getenv ("INDEX_URL" )
13+ INDEX_FILE = os .getenv ("INDEX_FILE" , "__index__.json" )
14+ UPLOAD_HOST = os .getenv ("UPLOAD_HOST" )
15+ UPLOAD_HOST_KEY = os .getenv ("UPLOAD_HOST_KEY" )
1516UPLOAD_KEYFILE = os .getenv ("UPLOAD_KEYFILE" )
16- UPLOAD_USER = os .getenv ("PyDotOrgUsername " )
17+ UPLOAD_USER = os .getenv ("UPLOAD_USER " )
1718NO_UPLOAD = os .getenv ("NO_UPLOAD" )
1819
1920def find_cmd (env , exe ):
@@ -55,17 +56,15 @@ def _run(*args):
5556 with subprocess .Popen (
5657 args ,
5758 stdout = subprocess .PIPE ,
58- stderr = subprocess .PIPE ,
59+ stderr = subprocess .STDOUT ,
5960 encoding = "ascii" ,
6061 errors = "replace" ,
6162 ) as p :
62- out , err = p .communicate (None )
63+ out , _ = p .communicate (None )
6364 if out :
6465 print (out )
65- if err :
66- print (err )
6766 if p .returncode :
68- raise RunError (p .returncode , out , err )
67+ raise RunError (p .returncode , out )
6968
7069
7170def call_ssh (* args , allow_fail = True ):
@@ -84,12 +83,14 @@ def upload_ssh(source, dest):
8483 print ("Skipping upload of" , source , "because UPLOAD_HOST is missing" )
8584 return
8685 _run (* _std_args (PSCP ), source , f"{ UPLOAD_USER } @{ UPLOAD_HOST } :{ dest } " )
86+ call_ssh (f"chgrp downloads { dest } && chmod g-x,o+r { dest } " )
8787
8888
8989def download_ssh (source , dest ):
9090 if not UPLOAD_HOST :
9191 print ("Skipping download of" , source , "because UPLOAD_HOST is missing" )
9292 return
93+ Path (dest ).parent .mkdir (exist_ok = True , parents = True )
9394 _run (* _std_args (PSCP ), f"{ UPLOAD_USER } @{ UPLOAD_HOST } :{ source } " , dest )
9495
9596
@@ -100,7 +101,7 @@ def ls_ssh(dest):
100101 try :
101102 _run (* _std_args (PSCP ), "-ls" , f"{ UPLOAD_USER } @{ UPLOAD_HOST } :{ dest } " )
102103 except RunError as ex :
103- if not ex .args [2 ].rstrip ().endswith ("No such file or directory" ):
104+ if not ex .args [1 ].rstrip ().endswith ("No such file or directory" ):
104105 raise
105106 print (dest , "was not found" )
106107
@@ -121,6 +122,26 @@ def get_hashes(src):
121122 return {"sha256" : h .hexdigest ()}
122123
123124
125+ def trim_install (install ):
126+ return {k : v for k , v in install .items ()
127+ if k not in ("aliases" , "run-for" , "shortcuts" )}
128+
129+
130+ def validate_new_installs (installs ):
131+ ids = [i ["id" ] for i in installs ]
132+ id_set = set (ids )
133+ if len (id_set ) < len (ids ):
134+ for i in id_set :
135+ ids .remove (i )
136+ print ("WARNING: Duplicate id fields:" , * ids )
137+ install_fors = [n for i in installs for n in i ["install-for" ]]
138+ install_set = set (install_fors )
139+ if len (install_set ) < len (install_fors ):
140+ for i in install_set :
141+ install_fors .remove (i )
142+ print ("WARNING: Duplicate install-for tags:" , * install_fors )
143+
144+
124145def purge (url ):
125146 if not UPLOAD_HOST or NO_UPLOAD :
126147 print ("Skipping purge of" , url , "because UPLOAD_HOST is missing" )
@@ -130,7 +151,7 @@ def purge(url):
130151
131152
132153def calculate_uploads ():
133- for p in Path ().absolute ().glob ("__install *.json" ):
154+ for p in sorted ( Path ().absolute ().glob ("__install__. *.json" ) ):
134155 i = json .loads (p .read_bytes ())
135156 u = urlparse (i ["url" ])
136157 src = Path (u .path .rpartition ("/" )[- 1 ]).absolute ()
@@ -160,22 +181,22 @@ def hash_packages(uploads):
160181
161182INDEX_PATH = url2path (INDEX_URL )
162183try :
163- download_ssh (INDEX_PATH , "__index__.json" )
184+ download_ssh (INDEX_PATH , INDEX_FILE )
164185except RunError as ex :
165- err = ex .args [2 ]
186+ err = ex .args [1 ]
166187 if not err .rstrip ().endswith ("no such file or directory" ):
167188 raise
168189 index = {"versions" : []}
169190else :
170- with open ("__index__.json" , "r" , encoding = "utf-8" ) as f :
191+ with open (INDEX_FILE , "r" , encoding = "utf-8" ) as f :
171192 index = json .load (f )
172193
173194
174- # TODO: Sort?
175- index [ "versions" ][: 0 ] = [ i for i , * _ in UPLOADS ]
176-
195+ new_installs = [ trim_install ( i ) for i , * _ in UPLOADS ]
196+ validate_new_installs ( new_installs )
197+ index [ "versions" ][: 0 ] = new_installs
177198
178- with open ("__index__.json" , "w" , encoding = "utf-8" ) as f :
199+ with open (INDEX_FILE , "w" , encoding = "utf-8" ) as f :
179200 # Include an indent for sanity while testing.
180201 # We should probably remove it later for the size benefits.
181202 json .dump (index , f , indent = 1 )
@@ -187,16 +208,14 @@ def hash_packages(uploads):
187208for i , src , dest , sbom , sbom_dest in UPLOADS :
188209 print ("Uploading" , src , "to" , dest )
189210 destdir = dest .rpartition ("/" )[0 ]
190- call_ssh ("mkdir" , destdir , "&&" , " chgrp" , " downloads" , destdir , "&&" , " chmod" , " a+rx" , destdir )
211+ call_ssh (f "mkdir { destdir } && chgrp downloads { destdir } && chmod a+rx { destdir } " )
191212 upload_ssh (src , dest )
192- call_ssh ("chgrp" , "downloads" , dest , "&&" , "chmod" , "g-x,o+r" , dest )
193213 if sbom and sbom_dest :
194214 upload_ssh (sbom , sbom_dest )
195- call_ssh ("chgrp" , "downloads" , sbom_dest , "&&" , "chmod" , "g-x,o+r" , sbom_dest )
196215
197216
198- print ("Uploading __index__.json to" , INDEX_URL )
199- upload_ssh ("__index__.json" , INDEX_PATH )
217+ print ("Uploading" , INDEX_FILE , " to" , INDEX_URL )
218+ upload_ssh (INDEX_FILE , INDEX_PATH )
200219
201220
202221print ("Purging" , len (UPLOADS ), "uploaded files" )
0 commit comments