Skip to content

Commit 1ac5ce5

Browse files
committed
Improved merge-and-upload script
1 parent 91a28bf commit 1ac5ce5

File tree

3 files changed

+58
-57
lines changed

3 files changed

+58
-57
lines changed

windows-release/merge-and-upload.py

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@
77
from urllib.parse import urlparse
88
from 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")
1516
UPLOAD_KEYFILE = os.getenv("UPLOAD_KEYFILE")
16-
UPLOAD_USER = os.getenv("PyDotOrgUsername")
17+
UPLOAD_USER = os.getenv("UPLOAD_USER")
1718
NO_UPLOAD = os.getenv("NO_UPLOAD")
1819

1920
def 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

7170
def 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

8989
def 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+
124145
def 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

132153
def 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

161182
INDEX_PATH = url2path(INDEX_URL)
162183
try:
163-
download_ssh(INDEX_PATH, "__index__.json")
184+
download_ssh(INDEX_PATH, INDEX_FILE)
164185
except 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": []}
169190
else:
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):
187208
for 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

202221
print("Purging", len(UPLOADS), "uploaded files")

windows-release/stage-pack-pymanager.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,7 @@ jobs:
5050
- powershell: |
5151
$install = (gc -raw "${env:LAYOUT}\__install__.json") -replace '"":', '"_":' | ConvertFrom-Json
5252
$filename = Split-Path -Left $install.url
53-
# Deliberately move the __install__.json out. We need it to publish, but
54-
# it doesn't belong in the package.
55-
mv "${env:LAYOUT}\__install__.json" $env:INSTALL_JSON
53+
cp "${env:LAYOUT}\__install__.json" $env:INSTALL_JSON
5654
Compress-Archive -CompressionLevel Optimal "${env:LAYOUT}\*" "$(Build.ArtifactStagingDirectory)\$filename"
5755
env:
5856
LAYOUT: $(Pipeline.Workspace)\layout_pymanager_${{ Name }}

windows-release/stage-publish-pymanager.yml

Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -62,32 +62,15 @@ jobs:
6262
workingDirectory: $(Build.BinariesDirectory)
6363
displayName: 'Upload ZIPs'
6464
env:
65+
UPLOAD_URL_PREFIX: 'https://www.python.org/ftp/'
66+
UPLOAD_PATH_PREFIX: '/srv/www.python.org/ftp/'
67+
INDEX_URL: 'https://www.python.org/ftp/python/__index_windows__.json'
6568
INDEX_PATH: ${{ parameters.FeedPath }}
66-
67-
- powershell: |
68-
"Merging following files:"
69-
dir *.json
70-
SSH GET ${{ parameters.FeedPath }} __index__.json
71-
# TODO: Grab current __index__.json
72-
python -c $env:MERGE_SCRIPT "__index__.json" "*.json"
73-
# TODO: Upload file
74-
env:
75-
MERGE_SCRIPT: |
76-
import json, glob, hashlib, sys
77-
78-
with open(sys.argv[1], 'rb') as j1:
79-
j1 = json.load(f1)
80-
fs = [open(p, 'rb') for glob.glob(sys.argv[2])]
81-
print("Loaded", len(fs), "files to merge")
82-
js = [json.load(f) for f in fs]
83-
[f.close() for f in fs]
84-
# TODO: Sort?
85-
for j in js:
86-
fname = j["url"].rpartition("/")[2]
87-
j1["versions"][:0] = js
88-
with open('__index__.json', 'wb') as f1:
89-
json.dump(f1, j1)
90-
print("__index__.json now contains", len(j1["versions"]), "entries")
69+
INDEX_FILE: '$(Build.ArtifactStagingDirectory)\index\__index__.json'
70+
UPLOAD_HOST: $(PyDotOrgServer)
71+
UPLOAD_HOST_KEY: $(PyDotOrgHostKey)
72+
UPLOAD_KEYFILE: $(sshkey.secureFilePath)
73+
UPLOAD_USER: $(PyDotOrgUsername)
9174
9275
- ${{ each alg in parameters.HashAlgorithms }}:
9376
- powershell: |
@@ -105,8 +88,9 @@ jobs:
10588
workingDirectory: $(Build.BinariesDirectory)
10689
displayName: 'Generate hashes (${{ alg }})'
10790
108-
- task: PublishPipelineArtifact@0
91+
- publish: '$(Build.ArtifactStagingDirectory)\index'
92+
artifact: pymanager_index
93+
94+
- publish: '$(Build.ArtifactStagingDirectory)\hashes'
95+
artifact: hashes
10996
displayName: 'Publish Artifact: hashes'
110-
inputs:
111-
targetPath: '$(Build.ArtifactStagingDirectory)\hashes'
112-
artifactName: hashes

0 commit comments

Comments
 (0)