| 1 | #!/usr/bin/env bash
 | 
| 2 | #
 | 
| 3 | # Functions to invoke soil/web remotely.
 | 
| 4 | # 
 | 
| 5 | # soil/web is deployed manually, and then this runs at HEAD in the repo.  Every
 | 
| 6 | # CI run has an up-to-date copy.
 | 
| 7 | #
 | 
| 8 | # Usage:
 | 
| 9 | #   soil/web-worker.sh <function name>
 | 
| 10 | 
 | 
| 11 | set -o nounset
 | 
| 12 | set -o pipefail
 | 
| 13 | set -o errexit
 | 
| 14 | 
 | 
| 15 | REPO_ROOT=$(cd "$(dirname $0)/.."; pwd)
 | 
| 16 | 
 | 
| 17 | source soil/common.sh
 | 
| 18 | source test/tsv-lib.sh  # tsv2html
 | 
| 19 | source web/table/html.sh  # table-sort-{begin,end}
 | 
| 20 | 
 | 
| 21 | # ~/
 | 
| 22 | #   soil-web/   # executable files
 | 
| 23 | #    doctools/
 | 
| 24 | #      html_head.py
 | 
| 25 | #    soil/
 | 
| 26 | #      web.py
 | 
| 27 | #      web.sh
 | 
| 28 | #   travis-ci.oilshell.org/  # served over HTTP
 | 
| 29 | #     index.html
 | 
| 30 | #     web/
 | 
| 31 | #       base.css
 | 
| 32 | #       soil.css
 | 
| 33 | #     github-jobs/
 | 
| 34 | #       index.html
 | 
| 35 | #       3619/  # $GITHUB_RUN_NUMBER
 | 
| 36 | #         dev-minimal.wwz
 | 
| 37 | #         cpp-small.wwz
 | 
| 38 | #     srht-jobs/
 | 
| 39 | #       index.html
 | 
| 40 | #       22/  # $JOB_ID
 | 
| 41 | #         dev-minimal.wwz
 | 
| 42 | #       23   # $JOB_ID
 | 
| 43 | #         cpp-small.wwz
 | 
| 44 | 
 | 
| 45 | sshq() {
 | 
| 46 |   # Don't need commands module as I said here!
 | 
| 47 |   # http://www.oilshell.org/blog/2017/01/31.html
 | 
| 48 |   #
 | 
| 49 |   # This is Bernstein chaining through ssh.
 | 
| 50 | 
 | 
| 51 |   ssh $SOIL_USER@$SOIL_HOST "$(printf '%q ' "$@")"
 | 
| 52 | }
 | 
| 53 | 
 | 
| 54 | remote-rewrite-jobs-index() {
 | 
| 55 |   sshq soil-web/soil/web.sh rewrite-jobs-index "$@"
 | 
| 56 | }
 | 
| 57 | 
 | 
| 58 | remote-cleanup-jobs-index() {
 | 
| 59 |   local prefix=$1
 | 
| 60 |   # clean it up for real!
 | 
| 61 |   sshq soil-web/soil/web.sh cleanup-jobs-index "$prefix" false
 | 
| 62 | }
 | 
| 63 | 
 | 
| 64 | remote-cleanup-status-api() {
 | 
| 65 |   #sshq soil-web/soil/web.sh cleanup-status-api false
 | 
| 66 |   # 2024-07 - work around bug.  The logic in soil/web.sh doesn't seem right
 | 
| 67 |   sshq soil-web/soil/web.sh cleanup-status-api true
 | 
| 68 | }
 | 
| 69 | 
 | 
| 70 | my-scp() {
 | 
| 71 |   scp -o StrictHostKeyChecking=no "$@"
 | 
| 72 | }
 | 
| 73 | 
 | 
| 74 | my-ssh() {
 | 
| 75 |   ssh -o StrictHostKeyChecking=no "$@"
 | 
| 76 | }
 | 
| 77 | 
 | 
| 78 | scp-status-api() {
 | 
| 79 |   local run_id=${1:-TEST2-github-run-id}
 | 
| 80 |   local job_name=$2
 | 
| 81 | 
 | 
| 82 |   local status_file="_soil-jobs/$job_name.status.txt"
 | 
| 83 |   local remote_path="$SOIL_REMOTE_DIR/status-api/github/$run_id/$job_name"
 | 
| 84 | 
 | 
| 85 |   # We could make this one invocation of something like:
 | 
| 86 |   # cat $status_file | sshq soil/web.sh PUT $remote_path
 | 
| 87 | 
 | 
| 88 |   my-ssh $SOIL_USER_HOST "mkdir -p $(dirname $remote_path)"
 | 
| 89 | 
 | 
| 90 |   # the consumer should check if these are all zero
 | 
| 91 |   # note: the file gets RENAMED
 | 
| 92 |   my-scp $status_file "$SOIL_USER_HOST:$remote_path"
 | 
| 93 | }
 | 
| 94 | 
 | 
| 95 | scp-results() {
 | 
| 96 |   # could also use Travis known_hosts addon?
 | 
| 97 |   local prefix=$1  # srht- or ''
 | 
| 98 |   shift
 | 
| 99 | 
 | 
| 100 |   my-scp "$@" "$SOIL_USER_HOST:$SOIL_REMOTE_DIR/${prefix}jobs/"
 | 
| 101 | }
 | 
| 102 | 
 | 
| 103 | # Dummy that doesn't depend on results
 | 
| 104 | deploy-test-wwz() {
 | 
| 105 |   set -x
 | 
| 106 |   local out_name="$(date +%Y-%m-%d__%H-%M-%S)_test"
 | 
| 107 | 
 | 
| 108 |   local wwz=$out_name.wwz
 | 
| 109 | 
 | 
| 110 |   cat >index.html <<EOF
 | 
| 111 | <a href="build/oil-manifest.txt">build/oil-manifest.txt</a> <br/>
 | 
| 112 | <a href="build/opy-manifest.txt">build/opy-manifest.txt</a> <br/>
 | 
| 113 | <a href="env.txt">env.txt</a> <br/>
 | 
| 114 | EOF
 | 
| 115 | 
 | 
| 116 |   dump-env > env.txt
 | 
| 117 | 
 | 
| 118 |   zip -q $wwz env.txt index.html build/*.txt
 | 
| 119 | 
 | 
| 120 |   scp-results '' $wwz
 | 
| 121 | }
 | 
| 122 | 
 | 
| 123 | format-wwz-index() {
 | 
| 124 |   ### What's displayed in $ID.wwz/index.html
 | 
| 125 | 
 | 
| 126 |   local job_id=$1
 | 
| 127 |   local tsv=${2:-_tmp/soil/INDEX.tsv}
 | 
| 128 | 
 | 
| 129 |   soil-html-head "$job_id.wwz"
 | 
| 130 | 
 | 
| 131 |   cat <<EOF
 | 
| 132 |   <body class="width40">
 | 
| 133 |     <p id="home-link">
 | 
| 134 |         <a href="..">Up</a>
 | 
| 135 |       | <a href="/">Home</a>
 | 
| 136 |       | <a href="//oilshell.org/">oilshell.org</a>
 | 
| 137 |     </p>
 | 
| 138 | 
 | 
| 139 |     <h1>$job_id.wwz</h1>
 | 
| 140 | EOF
 | 
| 141 | 
 | 
| 142 |   echo '<ul>'
 | 
| 143 |   cat <<EOF
 | 
| 144 |   <li>
 | 
| 145 |     <a href="_tmp/soil/INDEX.tsv">_tmp/soil/INDEX.tsv</a>, also copied to
 | 
| 146 |     <a href="../$job_id.tsv">../$job_id.tsv</a>.
 | 
| 147 |   </li>
 | 
| 148 |   <li>
 | 
| 149 |     <a href="../$job_id.json">../$job_id.json</a>
 | 
| 150 |   </li>
 | 
| 151 | EOF
 | 
| 152 | 
 | 
| 153 |   if test -f _tmp/soil/image.html; then
 | 
| 154 |     echo '
 | 
| 155 |     <li>
 | 
| 156 |       <a href="_tmp/soil/image.html">Container Image Stats</a>
 | 
| 157 |     </li>
 | 
| 158 |     '
 | 
| 159 |   fi
 | 
| 160 | 
 | 
| 161 |   echo '</ul>'
 | 
| 162 | }
 | 
| 163 | 
 | 
| 164 | format-image-stats() {
 | 
| 165 |   local soil_dir=${1:-_tmp/soil}
 | 
| 166 |   local web_base_url=${2:-'/web'}  # for production
 | 
| 167 | 
 | 
| 168 |   table-sort-html-head "Image Stats" $web_base_url
 | 
| 169 | 
 | 
| 170 |   # prints <body>; make it wide for the shell commands
 | 
| 171 |   table-sort-begin "width60"
 | 
| 172 | 
 | 
| 173 |   # TODO:
 | 
| 174 |   # - Format the TSV as an HTML table
 | 
| 175 |   # - Save the name and tag and show it
 | 
| 176 | 
 | 
| 177 |   cat <<EOF
 | 
| 178 |     <p id="home-link">
 | 
| 179 |         <a href="/">Home</a>
 | 
| 180 |       | <a href="//oilshell.org/">oilshell.org</a>
 | 
| 181 |     </p>
 | 
| 182 | 
 | 
| 183 |     <h1>Images Tagged</h1>
 | 
| 184 | 
 | 
| 185 |     <a href="images-tagged.txt">images-tagged.txt</a> <br/>
 | 
| 186 | 
 | 
| 187 |     <h1>Image Layers</h1>
 | 
| 188 | EOF
 | 
| 189 | 
 | 
| 190 |   tsv2html3 $soil_dir/image-layers.tsv
 | 
| 191 | 
 | 
| 192 |   # First column is number of bytes; ignore header
 | 
| 193 |   local total_bytes=$(awk '
 | 
| 194 |       { sum += $1 }
 | 
| 195 |   END { printf("%.1f", sum / 1000000) }
 | 
| 196 |   ' $soil_dir/image-layers.tsv)
 | 
| 197 | 
 | 
| 198 |   echo "<p>Total Size: <b>$total_bytes MB</b></p>"
 | 
| 199 | 
 | 
| 200 | 
 | 
| 201 |   cat <<EOF
 | 
| 202 |     <h2>Raw Data</h2>
 | 
| 203 | 
 | 
| 204 |     <a href="image-layers.txt">image-layers.txt</a> <br/>
 | 
| 205 |     <a href="image-layers.tsv">image-layers.tsv</a> <br/>
 | 
| 206 |   </body>
 | 
| 207 | </html>
 | 
| 208 | EOF
 | 
| 209 | 
 | 
| 210 |   table-sort-end image-layers
 | 
| 211 | }
 | 
| 212 | 
 | 
| 213 | make-job-wwz() {
 | 
| 214 |   local job_id=${1:-test-job}
 | 
| 215 | 
 | 
| 216 |   local wwz=$job_id.wwz
 | 
| 217 | 
 | 
| 218 |   # Doesn't exist when we're not using a container
 | 
| 219 |   if test -f _tmp/soil/image-layers.tsv; then
 | 
| 220 |     format-image-stats _tmp/soil > _tmp/soil/image.html
 | 
| 221 |   fi
 | 
| 222 | 
 | 
| 223 |   format-wwz-index $job_id > index.html
 | 
| 224 | 
 | 
| 225 |   # _tmp/soil: Logs are in _tmp, see soil/worker.sh
 | 
| 226 |   # web/ : spec test HTML references this.
 | 
| 227 |   #        Note that that index references /web/{base,soil}.css, outside the .wwz
 | 
| 228 |   #        osh-summary.html uses table-sort.js and ajax.js
 | 
| 229 |   #
 | 
| 230 |   # TODO:
 | 
| 231 |   # - Could move _tmp/{spec,stateful,syscall} etc. to _test
 | 
| 232 |   # - Create _tmp/benchmarks/{compute,gc,gc-cachegrind,osh-parser,mycpp-examples,...}
 | 
| 233 |   #   - would require release/$VERSION/pub/benchmarks.wwz, like we have
 | 
| 234 |   #     pub/metrics.wwz, for consistent links
 | 
| 235 | 
 | 
| 236 |   zip -q -r $wwz \
 | 
| 237 |     index.html \
 | 
| 238 |     _build/wedge/logs \
 | 
| 239 |     _test \
 | 
| 240 |     _tmp/{soil,spec,src-tree-www,wild-www,stateful,process-table,syscall,benchmark-data,metrics,mycpp-examples,compute,gc,gc-cachegrind,perf,vm-baseline,osh-runtime,osh-parser,host-id,shell-id} \
 | 
| 241 |     _tmp/uftrace/{index.html,stage2} \
 | 
| 242 |     web/{base,src-tree,spec-tests,spec-cpp,line-counts,benchmarks,wild}.css web/ajax.js \
 | 
| 243 |     web/table/table-sort.{css,js} \
 | 
| 244 |     _release/oil*.tar _release/*.xshar _release/VERSION/
 | 
| 245 | }
 | 
| 246 | 
 | 
| 247 | test-collect-json() {
 | 
| 248 |   soil/collect_json.py _tmp/soil PATH
 | 
| 249 | }
 | 
| 250 | 
 | 
| 251 | deploy-job-results() {
 | 
| 252 |   ### Copy .wwz, .tsv, and .json to a new dir
 | 
| 253 | 
 | 
| 254 |   local prefix=$1  # e.g. example.com/github-jobs/
 | 
| 255 |   local subdir=$2  # e.g. example.com/github-jobs/1234/  # make this dir
 | 
| 256 |   local job_name=$3  # e.g. example.com/github-jobs/1234/foo.wwz
 | 
| 257 |   shift 2
 | 
| 258 |   # rest of args are more env vars
 | 
| 259 | 
 | 
| 260 |   # writes $job_name.wwz
 | 
| 261 |   make-job-wwz $job_name
 | 
| 262 | 
 | 
| 263 |   # Debug permissions.  When using docker rather than podman, these dirs can be
 | 
| 264 |   # owned by root and we can't write into them.
 | 
| 265 |   ls -l -d _tmp/soil
 | 
| 266 |   ls -l _tmp/soil
 | 
| 267 | 
 | 
| 268 |   date +%s > _tmp/soil/task-deploy-start-time.txt
 | 
| 269 | 
 | 
| 270 |   soil/collect_json.py _tmp/soil "$@" > $job_name.json
 | 
| 271 | 
 | 
| 272 |   # So we don't have to unzip it
 | 
| 273 |   cp _tmp/soil/INDEX.tsv $job_name.tsv
 | 
| 274 | 
 | 
| 275 |   local remote_dest_dir="$SOIL_REMOTE_DIR/${prefix}jobs/$subdir"
 | 
| 276 |   my-ssh $SOIL_USER_HOST "mkdir -p $remote_dest_dir"
 | 
| 277 | 
 | 
| 278 |   # Do JSON last because that's what 'list-json' looks for
 | 
| 279 |   my-scp $job_name.{wwz,tsv,json} "$SOIL_USER_HOST:$remote_dest_dir"
 | 
| 280 | 
 | 
| 281 |   log ''
 | 
| 282 |   log 'View CI results here:'
 | 
| 283 |   log ''
 | 
| 284 |   log "http://$SOIL_HOST/${prefix}jobs/$subdir/"
 | 
| 285 |   log "http://$SOIL_HOST/${prefix}jobs/$subdir/$job_name.wwz/"
 | 
| 286 |   log ''
 | 
| 287 | }
 | 
| 288 | 
 | 
| 289 | publish-cpp-tarball() {
 | 
| 290 |   local prefix=${1:-'github-'}  # e.g. example.com/github-jobs/
 | 
| 291 | 
 | 
| 292 |   # Example of dir structure we need to cleanup:
 | 
| 293 |   #
 | 
| 294 |   # srht-jobs/
 | 
| 295 |   #   git-$hash/
 | 
| 296 |   #     index.html
 | 
| 297 |   #     oils-for-unix.tar
 | 
| 298 |   # github-jobs/
 | 
| 299 |   #   git-$hash/
 | 
| 300 |   #     oils-for-unix.tar
 | 
| 301 |   #
 | 
| 302 |   # Algorithm
 | 
| 303 |   # 1. List all JSON, finding commit date and commit hash
 | 
| 304 |   # 2. Get the OLDEST commit dates, e.g. all except for 50
 | 
| 305 |   # 3. Delete all commit hash dirs not associated with them
 | 
| 306 | 
 | 
| 307 |   # Fix subtle problem here !!!
 | 
| 308 |   shopt -s inherit_errexit
 | 
| 309 | 
 | 
| 310 |   local git_commit_dir
 | 
| 311 |   git_commit_dir=$(git-commit-dir "$prefix")
 | 
| 312 | 
 | 
| 313 |   my-ssh $SOIL_USER_HOST "mkdir -p $git_commit_dir"
 | 
| 314 | 
 | 
| 315 |   # Do JSON last because that's what 'list-json' looks for
 | 
| 316 | 
 | 
| 317 |   local tar=_release/oils-for-unix.tar 
 | 
| 318 | 
 | 
| 319 |   # Permission denied because of host/guest issue
 | 
| 320 |   #local tar_gz=$tar.gz
 | 
| 321 |   #gzip -c $tar > $tar_gz
 | 
| 322 | 
 | 
| 323 |   # Avoid race condition
 | 
| 324 |   # Crappy UUID: seconds since epoch, plus PID
 | 
| 325 |   local timestamp
 | 
| 326 |   timestamp=$(date +%s)
 | 
| 327 | 
 | 
| 328 |   local temp_name="tmp-$timestamp-$$.tar"
 | 
| 329 | 
 | 
| 330 |   my-scp $tar "$SOIL_USER_HOST:$git_commit_dir/$temp_name"
 | 
| 331 | 
 | 
| 332 |   my-ssh $SOIL_USER_HOST \
 | 
| 333 |     "mv -v $git_commit_dir/$temp_name $git_commit_dir/oils-for-unix.tar"
 | 
| 334 | 
 | 
| 335 |   log 'Tarball:'
 | 
| 336 |   log ''
 | 
| 337 |   log "http://$git_commit_dir"
 | 
| 338 | }
 | 
| 339 | 
 | 
| 340 | remote-event-job-done() {
 | 
| 341 |   ### "Client side" handler: a job calls this when it's done
 | 
| 342 | 
 | 
| 343 |   log "remote-event-job-done"
 | 
| 344 | 
 | 
| 345 |   # Deployed code dir
 | 
| 346 |   sshq soil-web/soil/web.sh event-job-done "$@"
 | 
| 347 | }
 | 
| 348 | 
 | 
| 349 | filename=$(basename $0)
 | 
| 350 | if test $filename = 'web-worker.sh'; then
 | 
| 351 |   "$@"
 | 
| 352 | fi
 |