Skip to content

Commit 8bda5c6

Browse files
committed
Move wheel publishing logic into a separate workflow that would run in target repo context
1 parent 18c8e36 commit 8bda5c6

2 files changed

Lines changed: 312 additions & 294 deletions

File tree

.github/workflows/build-ubuntu.yml

Lines changed: 0 additions & 294 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)