1 <#
2 .Synopsis
3     Uploads from a VSTS release build layout to python.org
4 .Description
5     Given the downloaded/extracted build artifact from a release
6     build run on python.visualstudio.com, this script uploads
7     the files to the correct locations.
8 .Parameter build
9     The location on disk of the extracted build artifact.
10 .Parameter user
11     The username to use when logging into the host.
12 .Parameter server
13     The host or PuTTY session name.
14 .Parameter target
15     The subdirectory on the host to copy files to.
16 .Parameter tests
17     The path to run download tests in.
18 .Parameter doc_htmlhelp
19     Optional path besides -build to locate CHM files.
20 .Parameter embed
21     Optional path besides -build to locate ZIP files.
22 .Parameter skipupload
23     Skip uploading
24 .Parameter skippurge
25     Skip purging the CDN
26 .Parameter skiptest
27     Skip the download tests
28 .Parameter skiphash
29     Skip displaying hashes
30 #>
31 param(
32     [Parameter(Mandatory=$true)][string]$build,
33     [Parameter(Mandatory=$true)][string]$user,
34     [string]$server="python-downloads",
35     [string]$target="/srv/www.python.org/ftp/python",
36     [string]$tests=${env:TEMP},
37     [string]$doc_htmlhelp=$null,
38     [string]$embed=$null,
39     [switch]$skipupload,
40     [switch]$skippurge,
41     [switch]$skiptest,
42     [switch]$skiphash
43 )
44 
45 if (-not $build) { throw "-build option is required" }
46 if (-not $user) { throw "-user option is required" }
47 
48 $tools = $script:MyInvocation.MyCommand.Path | Split-Path -parent;
49 
50 if (-not ((Test-Path "$build\win32\python-*.exe") -or (Test-Path "$build\amd64\python-*.exe"))) {
51     throw "-build argument does not look like a 'build' directory"
52 }
53 
find-putty-tool()54 function find-putty-tool {
55     param ([string]$n)
56     $t = gcm $n -EA 0
57     if (-not $t) { $t = gcm ".\$n" -EA 0 }
58     if (-not $t) { $t = gcm "${env:ProgramFiles}\PuTTY\$n" -EA 0 }
59     if (-not $t) { $t = gcm "${env:ProgramFiles(x86)}\PuTTY\$n" -EA 0 }
60     if (-not $t) { throw "Unable to locate $n.exe. Please put it on $PATH" }
61     return gi $t.Path
62 }
63 
64 $p = gci -r "$build\python-*.exe" | `
65     ?{ $_.Name -match '^python-(\d+\.\d+\.\d+)((a|b|rc)\d+)?-.+' } | `
66     select -first 1 | `
67     %{ $Matches[1], $Matches[2] }
68 
69 "Uploading version $($p[0]) $($p[1])"
70 "  from: $build"
71 "    to: $($server):$target/$($p[0])"
72 ""
73 
74 if (-not $skipupload) {
75     # Upload files to the server
76     $pscp = find-putty-tool "pscp"
77     $plink = find-putty-tool "plink"
78 
79     "Upload using $pscp and $plink"
80     ""
81 
82     if ($doc_htmlhelp) {
83         pushd $doc_htmlhelp
84     } else {
85         pushd $build
86     }
87     $chm = gci python*.chm, python*.chm.asc
88     popd
89 
90     $d = "$target/$($p[0])/"
91     & $plink -batch $user@$server mkdir $d
92     & $plink -batch $user@$server chgrp downloads $d
93     & $plink -batch $user@$server chmod g-x,o+rx $d
94     & $pscp -batch $chm.FullName "$user@${server}:$d"
95     if (-not $?) { throw "Failed to upload $chm" }
96 
97     $dirs = gci "$build" -Directory
98     if ($embed) {
99         $dirs = ($dirs, (gi $embed)) | %{ $_ }
100     }
101 
102     foreach ($a in $dirs) {
103         "Uploading files from $($a.FullName)"
104         pushd "$($a.FullName)"
105         $exe = gci *.exe, *.exe.asc, *.zip, *.zip.asc
106         $msi = gci *.msi, *.msi.asc, *.msu, *.msu.asc
107         popd
108 
109         if ($exe) {
110             & $pscp -batch $exe.FullName "$user@${server}:$d"
111             if (-not $?) { throw "Failed to upload $exe" }
112         }
113 
114         if ($msi) {
115             $sd = "$d$($a.Name)$($p[1])/"
116             & $plink -batch $user@$server mkdir $sd
117             & $plink -batch $user@$server chgrp downloads $sd
118             & $plink -batch $user@$server chmod g-x,o+rx $sd
119             & $pscp -batch $msi.FullName "$user@${server}:$sd"
120             if (-not $?) { throw "Failed to upload $msi" }
121             & $plink -batch $user@$server chgrp downloads $sd*
122             & $plink -batch $user@$server chmod g-x,o+r $sd*
123         }
124     }
125 
126     & $plink -batch $user@$server chgrp downloads $d*
127     & $plink -batch $user@$server chmod g-x,o+r $d*
128     & $pscp -ls "$user@${server}:$d"
129 }
130 
131 if (-not $skippurge) {
132     # Run a CDN purge
133     py $tools\purge.py "$($p[0])$($p[1])"
134 }
135 
136 if (-not $skiptest) {
137     # Use each web installer to produce a layout. This will download
138     # each referenced file and validate their signatures/hashes.
139     gci "$build\*-webinstall.exe" -r -File | %{
140         $d = mkdir "$tests\$($_.BaseName)" -Force
141         gci $d -r -File | del
142         $ic = copy $_ $d -PassThru
143         "Checking layout for $($ic.Name)"
144         Start-Process -wait $ic "/passive", "/layout", "$d\layout", "/log", "$d\log\install.log"
145         if (-not $?) {
146             Write-Error "Failed to validate layout of $($inst.Name)"
147         }
148     }
149 }
150 
151 if (-not $skiphash) {
152     # Display MD5 hash and size of each downloadable file
153     pushd $build
154     $files = gci python*.chm, *\*.exe, *\*.zip
155     if ($doc_htmlhelp) {
156         cd $doc_htmlhelp
157         $files = ($files, (gci python*.chm)) | %{ $_ }
158     }
159     if ($embed) {
160         cd $embed
161         $files = ($files, (gci *.zip)) | %{ $_ }
162     }
163     popd
164 
165     $hashes = $files | `
166         Sort-Object Name | `
167         Format-Table Name, @{Label="MD5"; Expression={(Get-FileHash $_ -Algorithm MD5).Hash}}, Length -AutoSize | `
168         Out-String -Width 4096
169     $hashes | clip
170     $hashes
171 }
172