@@ -332,297 +332,3 @@ jobs:
332332 exit 1
333333 fi
334334 done
335-
336- publish-wheel : # to internal Artifactory
337- runs-on : [self-hosted, linux]
338- needs :
339- - build-ubuntu
340- - test-cloudxr
341- - test-teleop-ros2
342- outputs :
343- wheel_paths : ${{ steps.upload-artifactory.outputs.wheel_paths }}
344- environment : dev
345- # Publish only after build and CloudXR tests succeed.
346- if : ${{ needs.build-ubuntu.result == 'success' && needs.test-cloudxr.result == 'success' && needs.test-teleop-ros2.result == 'success' }}
347-
348- steps :
349- - name : Set up Python
350- uses : actions/setup-python@v6
351- with :
352- python-version : ' 3.11'
353-
354- - name : Ensure clean wheels directory
355- run : |
356- rm -rf wheels
357-
358- - name : Download wheel artifacts
359- uses : actions/download-artifact@v7
360- with :
361- pattern : isaacteleop-wheels-*
362- merge-multiple : true
363- path : wheels
364-
365- - name : Upload wheel(s) to Artifactory
366- id : upload-artifactory
367- env :
368- ARTIFACTORY_URL : ${{ secrets.ARTIFACTORY_URL }}
369- ARTIFACTORY_REPO : ${{ secrets.ARTIFACTORY_REPO }}
370- ARTIFACTORY_USERNAME : ${{ secrets.ARTIFACTORY_USERNAME }}
371- ARTIFACTORY_API_KEY : ${{ secrets.ARTIFACTORY_API_KEY }}
372- run : |
373- set -euo pipefail
374-
375- if [[ -z "${ARTIFACTORY_URL}" || -z "${ARTIFACTORY_REPO}" || -z "${ARTIFACTORY_USERNAME}" || -z "${ARTIFACTORY_API_KEY}" ]]; then
376- echo "Missing one or more required secrets: ARTIFACTORY_URL, ARTIFACTORY_REPO, ARTIFACTORY_USERNAME, ARTIFACTORY_API_KEY"
377- exit 1
378- fi
379-
380- if [[ "${ARTIFACTORY_URL}" != https://* ]]; then
381- echo "ARTIFACTORY_URL must use https://"
382- exit 1
383- fi
384-
385- shopt -s nullglob
386- wheels=(wheels/*.whl)
387- if (( ${#wheels[@]} == 0 )); then
388- echo "No wheels found under wheels/*.whl"
389- ls -la wheels || true
390- exit 1
391- fi
392-
393- python -m pip install --upgrade pip twine
394-
395- # Artifactory PyPI repositories use the PyPI API endpoint.
396- repository_url="${ARTIFACTORY_URL%/}/api/pypi/${ARTIFACTORY_REPO}"
397- echo "Publishing ${#wheels[@]} wheel(s)"
398-
399- python -m twine upload \
400- --non-interactive \
401- --repository-url "${repository_url}" \
402- -u "${ARTIFACTORY_USERNAME}" \
403- -p "${ARTIFACTORY_API_KEY}" \
404- "${wheels[@]}"
405-
406- wheel_base="${ARTIFACTORY_URL%/}/${ARTIFACTORY_REPO}"
407- wheel_prefix="${wheel_base}/"
408-
409- wheel_paths=()
410- for wheel in "${wheels[@]}"; do
411- wheel_name="$(basename "${wheel}")"
412- echo "Resolving Artifactory URL for ${wheel_name}"
413-
414- search_json="$(curl --fail-with-body --show-error --silent --location --get --connect-timeout 10 --max-time 60 \
415- -u "${ARTIFACTORY_USERNAME}:${ARTIFACTORY_API_KEY}" \
416- --data-urlencode "name=${wheel_name}" \
417- --data-urlencode "repos=${ARTIFACTORY_REPO}" \
418- "${ARTIFACTORY_URL%/}/api/search/artifact")"
419-
420- result_count="$(jq '.results | length' <<< "${search_json}")"
421- if [[ "${result_count}" -ne 1 ]]; then
422- echo "Expected exactly 1 Artifactory search result for ${wheel_name}, but got ${result_count}"
423- echo "${search_json}"
424- exit 1
425- fi
426-
427- storage_uri="$(jq -r '.results[0].uri // empty' <<< "${search_json}")"
428- if [[ -z "${storage_uri}" ]]; then
429- echo "Unable to resolve storage URI for ${wheel_name}"
430- echo "${search_json}"
431- exit 1
432- fi
433-
434- metadata_json="$(curl --fail-with-body --show-error --silent --location --connect-timeout 10 --max-time 60 \
435- -u "${ARTIFACTORY_USERNAME}:${ARTIFACTORY_API_KEY}" \
436- "${storage_uri}")"
437-
438- download_uri="$(jq -r '.downloadUri // empty' <<< "${metadata_json}")"
439- if [[ -z "${download_uri}" ]]; then
440- echo "Unable to resolve downloadUri for ${wheel_name}"
441- echo "${metadata_json}"
442- exit 1
443- fi
444-
445- wheel_path="${download_uri#${wheel_prefix}}"
446- if [[ "${wheel_path}" == "${download_uri}" || -z "${wheel_path}" ]]; then
447- echo "Unable to clip Artifactory prefix from downloadUri for ${wheel_name}"
448- echo "Expected prefix: ${wheel_prefix}"
449- echo "${metadata_json}"
450- exit 1
451- fi
452-
453- wheel_paths+=("${wheel_path}")
454- done
455-
456- {
457- echo "wheel_paths<<EOF"
458- printf '%s\n' "${wheel_paths[@]}"
459- echo "EOF"
460- } >> "$GITHUB_OUTPUT"
461-
462- kitmaker :
463- runs-on : [self-hosted, linux]
464- needs :
465- - publish-wheel
466- outputs :
467- release_uuid : ${{ steps.submit-kitmaker.outputs.release_uuid }}
468- environment : release
469- permissions :
470- contents : read
471- if : ${{ github.event_name != 'pull_request' && needs.publish-wheel.result == 'success' }}
472-
473- steps :
474- - name : Submit wheel release to Kitmaker
475- id : submit-kitmaker
476- env :
477- KITMAKER_API_ENDPOINT : ${{ secrets.KITMAKER_API_ENDPOINT }}
478- KITMAKER_PROJECT_ID : ${{ secrets.KITMAKER_PROJECT_ID }}
479- KITMAKER_TOKEN : ${{ secrets.KITMAKER_TOKEN }}
480- KITMAKER_PIC_EMAIL : ${{ secrets.KITMAKER_PIC_EMAIL }}
481- ARTIFACTORY_URL : ${{ secrets.ARTIFACTORY_URL }}
482- ARTIFACTORY_REPO : ${{ secrets.ARTIFACTORY_REPO }}
483- WHEEL_PATHS : ${{ needs.publish-wheel.outputs.wheel_paths }}
484- KITMAKER_UPLOAD : ${{ startsWith(github.ref, 'refs/heads/release/') || startsWith(github.ref, 'refs/tags/v') }}
485- run : |
486- set -euo pipefail
487-
488- if [[ -z "${KITMAKER_API_ENDPOINT}" || -z "${KITMAKER_PROJECT_ID}" || -z "${KITMAKER_TOKEN}" || -z "${KITMAKER_PIC_EMAIL}" || -z "${ARTIFACTORY_URL}" || -z "${ARTIFACTORY_REPO}" ]]; then
489- echo "Missing one or more required secrets: KITMAKER_API_ENDPOINT, KITMAKER_PROJECT_ID, KITMAKER_TOKEN, KITMAKER_PIC_EMAIL, ARTIFACTORY_URL, ARTIFACTORY_REPO"
490- exit 1
491- fi
492-
493- if [[ "${KITMAKER_API_ENDPOINT}" != https://* ]]; then
494- echo "KITMAKER_API_ENDPOINT must use https://"
495- exit 1
496- fi
497-
498- if [[ "${ARTIFACTORY_URL}" != https://* ]]; then
499- echo "ARTIFACTORY_URL must use https://"
500- exit 1
501- fi
502-
503- if [[ -z "${WHEEL_PATHS}" ]]; then
504- echo "No wheel paths were produced by publish-wheel job"
505- exit 1
506- fi
507-
508- wheel_base="${ARTIFACTORY_URL%/}/${ARTIFACTORY_REPO}"
509-
510- api_url="${KITMAKER_API_ENDPOINT%/}/v0/projects/${KITMAKER_PROJECT_ID}/releases"
511- payload_items=()
512- while IFS= read -r wheel_path; do
513- [[ -z "${wheel_path}" ]] && continue
514- wheel_url="${wheel_base}/${wheel_path}"
515- wheel_name="$(basename "${wheel_path}")"
516- echo "Queueing wheel for Kitmaker payload: ${wheel_name}"
517-
518- payload_items+=("$(jq -cn \
519- --arg pic "${KITMAKER_PIC_EMAIL}" \
520- --arg url "${wheel_url}" \
521- --argjson upload "${KITMAKER_UPLOAD}" \
522- '{pic: $pic, job_type: "wheel-release-job", publish_to: "both_devzone_pypi", url: $url, size: "small", upload: $upload}')")
523- done <<< "${WHEEL_PATHS}"
524-
525- if (( ${#payload_items[@]} == 0 )); then
526- echo "No valid wheel URLs found to submit to Kitmaker"
527- exit 1
528- fi
529-
530- payload_array="$(printf '%s\n' "${payload_items[@]}" | jq -s '.')"
531- payload="$(jq -cn --arg project_name "isaacteleop" --argjson payload "${payload_array}" '{project_name: $project_name, payload: $payload}')"
532-
533- echo "Posting ${#payload_items[@]} wheel(s) to Kitmaker in a single request"
534- response_json="$(curl --fail-with-body --show-error --silent --location --connect-timeout 10 --max-time 120 \
535- -X POST "${api_url}" \
536- -H "Authorization: Bearer ${KITMAKER_TOKEN}" \
537- -H "Content-Type: application/json" \
538- -d "${payload}")"
539- echo "${response_json}"
540-
541- release_uuid="$(jq -r '.release_uuid // empty' <<< "${response_json}")"
542- if [[ -z "${release_uuid}" ]]; then
543- echo "Kitmaker response missing release_uuid"
544- exit 1
545- fi
546-
547- echo "release_uuid=${release_uuid}" >> "$GITHUB_OUTPUT"
548-
549- kitmaker-status :
550- runs-on : [self-hosted, linux]
551- needs :
552- - kitmaker
553- environment : release
554- permissions :
555- contents : read
556- if : ${{ github.event_name != 'pull_request' && needs.kitmaker.result == 'success' }}
557-
558- steps :
559- - name : Monitor Kitmaker release status
560- env :
561- KITMAKER_API_ENDPOINT : ${{ secrets.KITMAKER_API_ENDPOINT }}
562- KITMAKER_PROJECT_ID : ${{ secrets.KITMAKER_PROJECT_ID }}
563- KITMAKER_TOKEN : ${{ secrets.KITMAKER_TOKEN }}
564- KITMAKER_RELEASE_UUID : ${{ needs.kitmaker.outputs.release_uuid }}
565- ARTIFACTORY_URL : ${{ secrets.ARTIFACTORY_URL }}
566- ARTIFACTORY_REPO : ${{ secrets.ARTIFACTORY_REPO }}
567- run : |
568- set -euo pipefail
569-
570- # Redact sensitive values that may appear in API responses.
571- echo "::add-mask::${KITMAKER_API_ENDPOINT}"
572- echo "::add-mask::${KITMAKER_API_ENDPOINT%/}"
573- echo "::add-mask::${KITMAKER_RELEASE_UUID}"
574- echo "::add-mask::${KITMAKER_PROJECT_ID}"
575- echo "::add-mask::${ARTIFACTORY_URL}"
576- echo "::add-mask::${ARTIFACTORY_URL%/}"
577- echo "::add-mask::${ARTIFACTORY_REPO}"
578-
579- if [[ -z "${KITMAKER_API_ENDPOINT}" || -z "${KITMAKER_TOKEN}" ]]; then
580- echo "Missing required secrets: KITMAKER_API_ENDPOINT, KITMAKER_TOKEN"
581- exit 1
582- fi
583-
584- if [[ "${KITMAKER_API_ENDPOINT}" != https://* ]]; then
585- echo "KITMAKER_API_ENDPOINT must use https://"
586- exit 1
587- fi
588-
589- if [[ -z "${KITMAKER_RELEASE_UUID}" ]]; then
590- echo "No release_uuid was produced by the kitmaker job"
591- exit 1
592- fi
593-
594- status_url="${KITMAKER_API_ENDPOINT%/}/v0/status/${KITMAKER_RELEASE_UUID}"
595- # Limit total polling time to under an hour with exponential backoff starting at 30s
596- max_attempts=15
597- sleep_seconds=30
598-
599- for ((attempt=1; attempt<=max_attempts; attempt++)); do
600- echo "Polling Kitmaker status (attempt ${attempt}/${max_attempts})"
601-
602- response_json="$(curl --fail-with-body --show-error --silent --location --connect-timeout 10 --max-time 60 \
603- -H "Authorization: Bearer ${KITMAKER_TOKEN}" \
604- "${status_url}")"
605-
606- echo "${response_json}"
607- status="$(jq -r '.status // empty' <<< "${response_json}")"
608-
609- if [[ "${status}" == "completed" ]]; then
610- echo "Kitmaker release ${KITMAKER_RELEASE_UUID} completed"
611- exit 0
612- fi
613-
614- if [[ "${status}" == "failed" ]]; then
615- echo "Kitmaker release ${KITMAKER_RELEASE_UUID} failed"
616- exit 1
617- fi
618-
619- if (( attempt == max_attempts )); then
620- break
621- fi
622-
623- sleep "${sleep_seconds}"
624- sleep_seconds=$(( sleep_seconds * 125 / 100 ))
625- done
626-
627- echo "Timed out waiting for Kitmaker release ${KITMAKER_RELEASE_UUID} to complete"
628- exit 1
0 commit comments