Skip to content

Commit b0d4345

Browse files
committed
Move wheel publishing logic into a separate workflow that would run in target repo context
1 parent d033855 commit b0d4345

2 files changed

Lines changed: 313 additions & 301 deletions

File tree

.github/workflows/build-ubuntu.yml

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

0 commit comments

Comments
 (0)