diff --git a/.github/workflows-disabled/build.yml b/.github/workflows-disabled/build.yml new file mode 100644 index 00000000..e2541fd1 --- /dev/null +++ b/.github/workflows-disabled/build.yml @@ -0,0 +1,134 @@ +name: Release Build + +# Build xcframework on a GitHub-hosted macOS runner, resolving Swift package +# dependencies through the curated Artifactory pull-through cache (ADR Rule 3). +# Produces a build attestation binding the artifact digest to the source commit. + +on: + push: + pull_request: + workflow_dispatch: + +permissions: + contents: read + id-token: write + attestations: write + +jobs: + build_xcframework: + runs-on: macos-15 + env: + ARTIFACT_NAME: Segment.xcframework.zip + ARTIFACTORY_URL: https://twilio.jfrog.io + SWIFT_VIRTUAL_REPO: virtual-swift-thirdparty + outputs: + artifact-digest: ${{ steps.digest.outputs.sha256 }} + + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Select Xcode + run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + + # Exchange GitHub OIDC token for a short-lived Artifactory read token. + # The segmentio org uses provider_name "github-actions-segmentio". + # Token lands in sdk-build-readers group (read access to virtual repos). + - name: Get Artifactory token via OIDC + run: | + set -euo pipefail + OIDC_JWT=$(curl -sS "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=${ARTIFACTORY_URL}" \ + -H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" | jq -r '.value') + ART_TOKEN=$(curl -sS "${ARTIFACTORY_URL}/access/api/v1/oidc/token" \ + -H 'Content-Type: application/json' \ + -d "{\"grant_type\":\"urn:ietf:params:oauth:grant-type:token-exchange\", + \"subject_token_type\":\"urn:ietf:params:oauth:token-type:id_token\", + \"subject_token\":\"${OIDC_JWT}\", + \"provider_name\":\"github-actions-segmentio\"}" | jq -r '.access_token') + echo "::add-mask::$ART_TOKEN" + echo "ART_TOKEN=$ART_TOKEN" >> "$GITHUB_ENV" + + # Configure SPM to resolve dependencies through the Artifactory Swift + # pull-through cache instead of hitting GitHub directly. + - name: Configure SPM to resolve through Artifactory + run: | + set -euo pipefail + MIRROR_BASE="${ARTIFACTORY_URL}/artifactory/api/swift/${SWIFT_VIRTUAL_REPO}" + + # Set up netrc for git authentication to Artifactory + echo "machine twilio.jfrog.io login token password ${ART_TOKEN}" > ~/.netrc + chmod 600 ~/.netrc + + # Mirror each dependency through Artifactory + swift package config set-mirror \ + --original-url https://github.com/segmentio/sovran-swift.git \ + --mirror-url "${MIRROR_BASE}/segmentio/sovran-swift.git" + swift package config set-mirror \ + --original-url https://github.com/segmentio/jsonsafeencoding-swift.git \ + --mirror-url "${MIRROR_BASE}/segmentio/jsonsafeencoding-swift.git" + + - name: Resolve dependencies + run: swift package resolve + + # Build xcframework for all supported platforms + - name: Build xcframework + run: | + set -euo pipefail + + ARCHIVE_DIR="$(pwd)/.build/archives" + XCFRAMEWORK_DIR="$(pwd)/.build/xcframework" + mkdir -p "$ARCHIVE_DIR" "$XCFRAMEWORK_DIR" + + declare -a PLATFORMS=( + "iOS:generic/platform=iOS" + "iOS-Simulator:generic/platform=iOS Simulator" + "macOS:generic/platform=macOS" + "tvOS:generic/platform=tvOS" + "tvOS-Simulator:generic/platform=tvOS Simulator" + "watchOS:generic/platform=watchOS" + "watchOS-Simulator:generic/platform=watchOS Simulator" + ) + + FRAMEWORK_ARGS=() + for entry in "${PLATFORMS[@]}"; do + NAME="${entry%%:*}" + DEST="${entry#*:}" + ARCHIVE_PATH="$ARCHIVE_DIR/$NAME.xcarchive" + + xcodebuild archive \ + -scheme Segment \ + -destination "$DEST" \ + -archivePath "$ARCHIVE_PATH" \ + -derivedDataPath .build/derived-data \ + SKIP_INSTALL=NO \ + BUILD_LIBRARY_FOR_DISTRIBUTION=YES \ + SWIFT_SERIALIZE_DEBUGGING_OPTIONS=NO + + FRAMEWORK_ARGS+=("-framework" "$ARCHIVE_PATH/Products/Library/Frameworks/Segment.framework") + done + + xcodebuild -create-xcframework \ + "${FRAMEWORK_ARGS[@]}" \ + -output "$XCFRAMEWORK_DIR/Segment.xcframework" + + cd "$XCFRAMEWORK_DIR" + ditto -c -k --sequesterRsrc --keepParent Segment.xcframework "${{ env.ARTIFACT_NAME }}" + mv "${{ env.ARTIFACT_NAME }}" "$GITHUB_WORKSPACE/" + + - name: Compute artifact digest + id: digest + run: | + SHA256=$(shasum -a 256 "${{ env.ARTIFACT_NAME }}" | awk '{print $1}') + echo "sha256=$SHA256" >> "$GITHUB_OUTPUT" + echo "Artifact digest: $SHA256" + + - name: Attest build provenance + uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3 + with: + subject-path: ${{ env.ARTIFACT_NAME }} + + - name: Upload artifact + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: xcframework-release + path: ${{ env.ARTIFACT_NAME }} + retention-days: 30 diff --git a/.github/workflows-disabled/publish.yml b/.github/workflows-disabled/publish.yml new file mode 100644 index 00000000..83bde7c3 --- /dev/null +++ b/.github/workflows-disabled/publish.yml @@ -0,0 +1,109 @@ +name: Publish + +# Verify clearance and publish the exact cleared bytes to GitHub Release. +# +# This workflow: +# 1. Downloads the built artifact from the release-build run +# 2. Recomputes its sha256 digest +# 3. Verifies the build attestation (signer-workflow pinned) +# 4. Verifies internal clearance (placeholder — not yet built by Platform) +# 5. On all checks pass: uploads to GitHub Release +# 6. The sha256 becomes the checksum in Package.swift's binaryTarget +# +# FAILS CLOSED on any mismatch. + +on: + workflow_dispatch: + inputs: + version: + description: 'Version to publish (e.g., 1.9.5)' + required: true + run-id: + description: 'The release-build workflow run ID' + required: true + artifact-digest: + description: 'The sha256 digest from the release build' + required: true + +permissions: + contents: write + id-token: write + attestations: write + +env: + ARTIFACT_NAME: Segment.xcframework.zip + BUILD_WORKFLOW_REF: ".github/workflows/build.yml" + +jobs: + verify_and_publish: + runs-on: macos-26 + environment: production + + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Download release artifact + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: xcframework-release + run-id: ${{ inputs.run-id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + # Gate 1: Digest verification — detect substitution between build and publish. + - name: Verify artifact digest + run: | + set -euo pipefail + COMPUTED_DIGEST=$(shasum -a 256 "${{ env.ARTIFACT_NAME }}" | awk '{print $1}') + EXPECTED_DIGEST="${{ inputs.artifact-digest }}" + + if [ "$COMPUTED_DIGEST" != "$EXPECTED_DIGEST" ]; then + echo "::error::DIGEST MISMATCH — artifact may have been tampered with" + exit 1 + fi + + echo "Digest verified: $COMPUTED_DIGEST" + echo "ARTIFACT_DIGEST=$COMPUTED_DIGEST" >> "$GITHUB_ENV" + + # Gate 2: Build attestation — verify it was signed by the expected workflow. + - name: Verify build attestation + run: | + set -euo pipefail + gh attestation verify "${{ env.ARTIFACT_NAME }}" \ + -R "${{ github.repository }}" \ + --signer-workflow "${{ env.BUILD_WORKFLOW_REF }}" + + # Gate 3: Internal clearance — placeholder until Platform delivers the gate. + - name: Verify internal clearance + run: | + echo "PLACEHOLDER: Internal clearance gate not yet available (PEA-1323)" + echo "Once delivered, this step will:" + echo " 1. Fetch KMS-signed clearance attestation for digest ${{ env.ARTIFACT_DIGEST }}" + echo " 2. Verify signature against the clearance KMS public key" + echo " 3. Assert clearance subject.digest.sha256 == ${{ env.ARTIFACT_DIGEST }}" + echo " 4. Assert clearance predicate.sourceCommit == ${{ github.sha }}" + echo " 5. Fail closed on any mismatch" + + # All gates passed — publish to GitHub Release. + - name: Create GitHub Release + run: | + set -euo pipefail + VERSION="${{ inputs.version }}" + + gh release create "$VERSION" \ + "${{ env.ARTIFACT_NAME }}" \ + --title "Version $VERSION" \ + --notes "## Segment.xcframework ${VERSION} + + Binary Swift package release. + + ### Consumer verification + + Verify the build attestation: + \`\`\` + gh attestation verify Segment.xcframework.zip -R ${{ github.repository }} --signer-workflow ${{ env.BUILD_WORKFLOW_REF }} + \`\`\` + + ### Checksum (for Package.swift binaryTarget) + \`\`\` + ${{ env.ARTIFACT_DIGEST }} + \`\`\`" diff --git a/.github/workflows/create_jira.yml b/.github/workflows/create_jira.yml deleted file mode 100644 index 8180ac0f..00000000 --- a/.github/workflows/create_jira.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Create Jira Ticket - -on: - issues: - types: - - opened - -jobs: - create_jira: - name: Create Jira Ticket - runs-on: ubuntu-latest - environment: IssueTracker - steps: - - name: Checkout - uses: actions/checkout@master - - name: Login - uses: atlassian/gajira-login@master - env: - JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} - JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} - JIRA_API_TOKEN: ${{ secrets.JIRA_TOKEN }} - JIRA_EPIC_KEY: ${{ secrets.JIRA_EPIC_KEY }} - JIRA_PROJECT: ${{ secrets.JIRA_PROJECT }} - - - name: Create - id: create - uses: atlassian/gajira-create@master - with: - project: ${{ secrets.JIRA_PROJECT }} - issuetype: Bug - summary: | - [${{ github.event.repository.name }}] (${{ github.event.issue.number }}): ${{ github.event.issue.title }} - description: | - Github Link: ${{ github.event.issue.html_url }} - ${{ github.event.issue.body }} - fields: '{"parent": {"key": "${{ secrets.JIRA_EPIC_KEY }}"}}' - - - name: Log created issue - run: echo "Issue ${{ steps.create.outputs.issue }} was created" \ No newline at end of file diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml deleted file mode 100644 index 3103746c..00000000 --- a/.github/workflows/e2e-tests.yml +++ /dev/null @@ -1,73 +0,0 @@ -# E2E Tests for analytics-swift -# Copy this file to: analytics-swift/.github/workflows/e2e-tests.yml -# -# This workflow: -# 1. Checks out the SDK and sdk-e2e-tests repos -# 2. Applies the HTTP patch to allow mock server testing -# 3. Builds the e2e-cli (separate SPM package) -# 4. Runs the e2e test suite - -name: E2E Tests - -on: - push: - branches: [main, master] - pull_request: - branches: [main, master] - workflow_dispatch: - inputs: - e2e_tests_ref: - description: 'Branch or ref of sdk-e2e-tests to use' - required: false - default: 'main' - -jobs: - e2e-tests: - # Skip on fork PRs where repo secrets aren't available - if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} - runs-on: macos-latest - - steps: - - name: Checkout SDK - uses: actions/checkout@v4 - with: - path: sdk - - - name: Checkout sdk-e2e-tests - uses: actions/checkout@v4 - with: - repository: segmentio/sdk-e2e-tests - ref: ${{ inputs.e2e_tests_ref || 'main' }} - token: ${{ secrets.E2E_TESTS_TOKEN }} - path: sdk-e2e-tests - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Apply HTTP patch for testing - working-directory: sdk - run: | - git apply ../sdk-e2e-tests/patches/analytics-swift-http.patch - echo "HTTP patch applied successfully" - - - name: Build e2e-cli - working-directory: sdk/e2e-cli - run: swift build - - - name: Run E2E tests - working-directory: sdk-e2e-tests - run: | - ./scripts/run-tests.sh \ - --sdk-dir "${{ github.workspace }}/sdk/e2e-cli" \ - --cli "${{ github.workspace }}/sdk/e2e-cli/.build/debug/E2ECLI" \ - --sdk-path "${{ github.workspace }}/sdk" - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v4 - with: - name: e2e-test-results - path: sdk-e2e-tests/test-results/ - if-no-files-found: ignore diff --git a/.github/workflows/publish-e2e-cli.yml b/.github/workflows/publish-e2e-cli.yml deleted file mode 100644 index cac6666f..00000000 --- a/.github/workflows/publish-e2e-cli.yml +++ /dev/null @@ -1,36 +0,0 @@ -# Publish E2E CLI build as a GitHub Actions artifact -# -# On merge to main (or monthly refresh), builds the e2e-cli binary -# and uploads it as an artifact. -# -# Note: Binary is compiled for macOS (arm64). Add a Linux job if needed. - -name: Publish E2E CLI - -on: - push: - branches: [main] - paths: - - 'e2e-cli/**' - - 'Sources/**' - schedule: - - cron: '0 0 1 * *' - workflow_dispatch: - -jobs: - publish: - runs-on: macos-latest - steps: - - name: Checkout SDK - uses: actions/checkout@v4 - - - name: Build e2e-cli (release) - working-directory: e2e-cli - run: swift build -c release - - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: e2e-cli - path: e2e-cli/.build/release/E2ECLI - retention-days: 90 diff --git a/.github/workflows/resolve-dependencies.yml b/.github/workflows/resolve-dependencies.yml new file mode 100644 index 00000000..0a34a65c --- /dev/null +++ b/.github/workflows/resolve-dependencies.yml @@ -0,0 +1,98 @@ +name: Resolve Dependencies (Artifactory OIDC) + +# Validate that SPM dependencies resolve through the curated Artifactory +# pull-through cache via OIDC token exchange (ADR Rule 3). + +on: + push: + branches: [poc/**] + workflow_dispatch: + +permissions: + contents: read + id-token: write + +jobs: + resolve: + runs-on: ubuntu-latest-large + env: + ARTIFACTORY_URL: https://twilio.jfrog.io + SWIFT_VIRTUAL_REPO: virtual-swift-thirdparty + + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Get Artifactory token via OIDC + run: | + set -euo pipefail + OIDC_JWT=$(curl -sS "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=${ARTIFACTORY_URL}" \ + -H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" | jq -r '.value') + ART_TOKEN=$(curl -sS "${ARTIFACTORY_URL}/access/api/v1/oidc/token" \ + -H 'Content-Type: application/json' \ + -d "{\"grant_type\":\"urn:ietf:params:oauth:grant-type:token-exchange\", + \"subject_token_type\":\"urn:ietf:params:oauth:token-type:id_token\", + \"subject_token\":\"${OIDC_JWT}\", + \"provider_name\":\"github-actions-segmentio\"}" | jq -r '.access_token') + echo "::add-mask::$ART_TOKEN" + echo "ART_TOKEN=$ART_TOKEN" >> "$GITHUB_ENV" + + - name: Verify Artifactory token + run: | + set -euo pipefail + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \ + -H "Authorization: Bearer ${ART_TOKEN}" \ + "${ARTIFACTORY_URL}/artifactory/api/system/ping") + echo "Artifactory ping: HTTP $HTTP_CODE" + if [ "$HTTP_CODE" != "200" ]; then + echo "::error::Artifactory token is invalid (HTTP $HTTP_CODE)" + exit 1 + fi + + - name: Debug git credential setup + run: | + echo "HOME=$HOME" + echo "Runner user: $(whoami)" + swift --version + git --version + + - name: Probe Artifactory Swift URL formats + run: | + set -euo pipefail + PATHS=( + "artifactory/git/virtual-swift-thirdparty/segmentio/sovran-swift.git/info/refs?service=git-upload-pack" + "artifactory/git/virtual-swift-thirdparty/segmentio/sovran-swift/info/refs?service=git-upload-pack" + "artifactory/git/virtual-swift-thirdparty/sovran-swift.git/info/refs?service=git-upload-pack" + "artifactory/git/remote-swift-github/segmentio/sovran-swift.git/info/refs?service=git-upload-pack" + "artifactory/git/remote-swift-github/segmentio/sovran-swift/info/refs?service=git-upload-pack" + "artifactory/remote-swift-github/segmentio/sovran-swift.git/info/refs?service=git-upload-pack" + ) + for path in "${PATHS[@]}"; do + URL="${ARTIFACTORY_URL}/${path}" + RESPONSE=$(curl -s -w "\n%{http_code}" \ + -H "Authorization: Bearer ${ART_TOKEN}" "$URL") + HTTP_CODE=$(echo "$RESPONSE" | tail -1) + BODY=$(echo "$RESPONSE" | sed '$d' | head -5) + echo "$HTTP_CODE — $path" + if [ "$HTTP_CODE" != "404" ]; then + echo " Body: $BODY" + fi + done + + - name: Configure SPM to resolve through Artifactory + run: | + set -euo pipefail + MIRROR_BASE="${ARTIFACTORY_URL}/artifactory/api/swift/${SWIFT_VIRTUAL_REPO}" + + # Configure git to pass the token as a Bearer header for Artifactory + git config --global http.https://twilio.jfrog.io/.extraHeader \ + "Authorization: Bearer ${ART_TOKEN}" + + swift package config set-mirror \ + --original-url https://github.com/segmentio/sovran-swift.git \ + --mirror-url "${MIRROR_BASE}/segmentio/sovran-swift.git" + swift package config set-mirror \ + --original-url https://github.com/segmentio/jsonsafeencoding-swift.git \ + --mirror-url "${MIRROR_BASE}/segmentio/jsonsafeencoding-swift.git" + + - name: Resolve dependencies + run: swift package resolve diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml deleted file mode 100644 index e2c93ffd..00000000 --- a/.github/workflows/swift.yml +++ /dev/null @@ -1,134 +0,0 @@ -name: Swift - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - cancel_previous: - runs-on: ubuntu-latest - steps: - - uses: styfle/cancel-workflow-action@0.12.0 - with: - workflow_id: ${{ github.event.workflow.id }} - - generate_code_coverage: - needs: cancel_previous - runs-on: macos-26 - steps: - - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: "26" - - uses: actions/checkout@v2 - - uses: webfactory/ssh-agent@v0.8.0 - with: - ssh-private-key: ${{ secrets.SOVRAN_SSH_KEY }} - - name: Build & Run tests - run: swift test --enable-code-coverage - - name: Convert coverage report - run: xcrun llvm-cov export -format="lcov" .build/debug/SegmentPackageTests.xctest/Contents/MacOS/SegmentPackageTests -instr-profile .build/debug/codecov/default.profdata > coverage.lcov - - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v4.0.1 - with: - token: ${{ secrets.CODECOV_TOKEN }} - slug: segmentio/analytics-swift - - build_and_test_spm_mac: - needs: cancel_previous - runs-on: macos-26 - steps: - - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: "26" - - uses: actions/checkout@v2 - - uses: webfactory/ssh-agent@v0.8.0 - with: - ssh-private-key: ${{ secrets.SOVRAN_SSH_KEY }} - - name: Build & Run tests - run: swift test - - build_and_test_ios: - needs: cancel_previous - runs-on: macos-26 - steps: - - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: "26" - - uses: actions/checkout@v2 - - uses: webfactory/ssh-agent@v0.8.0 - with: - ssh-private-key: ${{ secrets.SOVRAN_SSH_KEY }} - - run: xcodebuild -scheme Segment test -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 17' - - build_and_test_tvos: - needs: cancel_previous - runs-on: macos-26 - steps: - - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: "26" - - uses: actions/checkout@v2 - - uses: webfactory/ssh-agent@v0.8.0 - with: - ssh-private-key: ${{ secrets.SOVRAN_SSH_KEY }} - - run: xcodebuild -scheme Segment test -sdk appletvsimulator -destination 'platform=tvOS Simulator,name=Apple TV' - - build_and_test_watchos: - needs: cancel_previous - runs-on: macos-26 - steps: - - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: "26" - - uses: actions/checkout@v2 - - uses: webfactory/ssh-agent@v0.8.0 - with: - ssh-private-key: ${{ secrets.SOVRAN_SSH_KEY }} - - run: xcodebuild -scheme Segment test -sdk watchsimulator -destination 'platform=watchOS Simulator,name=Apple Watch Ultra 3 (49mm)' - - build_and_test_visionos: - needs: cancel_previous - runs-on: macos-26 - steps: - - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: "26" - - uses: actions/checkout@v2 - - uses: webfactory/ssh-agent@v0.8.0 - with: - ssh-private-key: ${{ secrets.SOVRAN_SSH_KEY }} - - run: xcodebuild -scheme Segment test -destination 'platform=visionOS Simulator,OS=latest,name=Apple Vision Pro' - - build_and_test_examples: - needs: cancel_previous - runs-on: macos-26 - steps: - - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: "26" - - uses: actions/checkout@v2 - - uses: webfactory/ssh-agent@v0.8.0 - with: - ssh-private-key: ${{ secrets.SOVRAN_SSH_KEY }} - - name: build for ios simulator - run: | - cd Examples/apps/BasicExample - xcodebuild -workspace "BasicExample.xcworkspace" -scheme "BasicExample" -sdk iphonesimulator - - name: build for ios simulator - run: | - cd Examples/apps/ObjCExample - xcodebuild -workspace "ObjCExample.xcworkspace" -scheme "ObjCExample" -sdk iphonesimulator - - name: build for ios simulator - run: | - cd Examples/apps/SegmentUIKitExample - xcodebuild -workspace "SegmentUIKitExample.xcworkspace" -scheme "SegmentUIKitExample" -sdk iphonesimulator - - name: build for ios simulator - run: | - cd Examples/apps/SegmentWeatherWidget - xcodebuild -workspace "SegmentWeatherWidget.xcworkspace" -scheme "SegmentWeatherWidget" -sdk iphonesimulator - - name: build for mac catalyst - run: | - cd Examples/apps/SegmentUIKitExample - xcodebuild -workspace "SegmentUIKitExample.xcworkspace" -scheme "SegmentUIKitExample" -destination 'platform=macOS,variant=Mac Catalyst' diff --git a/docs/artifactory-swift-feedback.md b/docs/artifactory-swift-feedback.md new file mode 100644 index 00000000..08026638 --- /dev/null +++ b/docs/artifactory-swift-feedback.md @@ -0,0 +1,114 @@ +# Feedback: SPM Dependency Resolution via Artifactory + +**From:** Segment SDK team (analytics-swift) +**To:** Secure Supply Chain / Platform team +**Date:** 2026-07-02 +**Repo:** `segmentio/analytics-swift` +**Branch:** `poc/binary-xcframework-release-pipeline` + +## What works + +| Step | Status | Evidence | +| --- | --- | --- | +| OIDC token exchange (`github-actions-segmentio` provider) | Working | Token mints successfully, `segmentio/analytics-swift` is in the OIDC trust | +| Token validation (Artifactory REST API ping) | Working | `GET /artifactory/api/system/ping` returns HTTP 200 with the OIDC-issued token | + +## What doesn't work + +SPM cannot clone dependencies through Artifactory. Git clone fails regardless +of URL format or auth mechanism. + +### Auth mechanisms tried + +| Approach | Result | +| --- | --- | +| `~/.netrc` (`machine twilio.jfrog.io login token password $TOKEN`) | `could not read Username` — git on Linux didn't read it | +| `git credential.helper store` with `~/.git-credentials` (`https://token:$TOKEN@twilio.jfrog.io`) | `Authentication failed` | +| `git config url.insteadOf` (embed token in URL rewrite) | `repository not found` (404) | +| `git config http.extraHeader "Authorization: Bearer $TOKEN"` | `repository not found` (404) | + +The Bearer header approach is what the internal docs recommend for Swift. Once +auth was resolved, the underlying issue became clear: **the URL path returns 404 +or 500 regardless of auth**. + +### URL paths probed + +All probed with a valid Bearer token (confirmed via ping). + +| URL path | HTTP | Response | +| --- | --- | --- | +| `artifactory/api/swift/virtual-swift-thirdparty/segmentio/sovran-swift.git/info/refs` | 404 | Not found | +| `artifactory/git/virtual-swift-thirdparty/segmentio/sovran-swift.git/info/refs` | 500 | `"Expected Repository attribute"` | +| `artifactory/git/virtual-swift-thirdparty/segmentio/sovran-swift/info/refs` | 500 | `"Expected Repository attribute"` | +| `artifactory/git/virtual-swift-thirdparty/sovran-swift.git/info/refs` | 500 | `"Expected Repository attribute"` | +| `artifactory/git/remote-swift-github/segmentio/sovran-swift.git/info/refs` | 500 | `"Expected Repository attribute"` | +| `artifactory/git/remote-swift-github/segmentio/sovran-swift/info/refs` | 500 | `"Expected Repository attribute"` | +| `artifactory/remote-swift-github/segmentio/sovran-swift.git/info/refs` | 404 | Not found | +| `artifactory/virtual-swift-thirdparty/segmentio/sovran-swift.git/info/refs` | 404 | Not found | +| `artifactory/api/vcs/virtual-swift-thirdparty/segmentio/sovran-swift.git/info/refs` | 404 | Not found | + +## Root cause (our assessment) + +The `"Expected Repository attribute"` error on all `/git/` paths suggests the +Artifactory instance does not have a **Git LFS / Git repository type** enabled. +The Swift virtual repo (`virtual-swift-thirdparty`) is configured as a `swift` +package type, which likely supports the Swift Package Registry protocol +(SE-0292) but **not raw git clone over HTTPS** — which is what SPM uses to +resolve dependencies. + +SPM resolves packages by git-cloning the repository URL declared in +`Package.swift`. The `swift package config set-mirror` command redirects those +clones to a different URL, but the target must still be a git-cloneable +endpoint. + +## Proposed solutions + +We need one of the following to resolve Swift dependencies through Artifactory +in CI: + +1. **Provide a git-cloneable URL path** for the `virtual-swift-thirdparty` + repo. If there is a URL format that supports `git clone` over HTTPS with + a Bearer token, we need documentation on it. + +2. **Or, enable a git-type repository** in Artifactory that proxies + `https://github.com` and supports the git smart HTTP protocol + (`/info/refs?service=git-upload-pack`). The current Swift-type repo does + not support raw git clone. + +3. **Or, provide documentation on Swift Package Registry (SE-0292) mode** if + that's how the Swift virtual repo is meant to be consumed. SPM 5.7+ supports + registry-based resolution (`swift package-registry set`), but this is a + different mechanism than git mirrors and we have no example of it working + against this Artifactory instance. + +## Questions + +1. **Does `virtual-swift-thirdparty` support git clone?** If so, what is the + correct URL format? The docs say to "configure SPM to resolve through + `https://twilio.jfrog.io/artifactory/api/swift/virtual-swift-thirdparty/`" + but don't specify the git clone path. + +2. **If it only supports the Swift Package Registry protocol (SE-0292):** is + there documentation on how to configure SPM to use registry mode instead of + git mode? SPM 5.7+ has `swift package-registry set`, but this is a different + resolution mechanism than git-based mirrors. + +3. **Is a separate git-type repository needed?** Would the solution be to + create a `remote-git-github` repository (Artifactory Git LFS type) that + proxies `https://github.com` and can serve git clone requests? + +## Environment + +- Runner: `ubuntu-latest-large` (GitHub-hosted) +- Swift: version available on the runner image +- Git: version available on the runner image +- Artifactory: `twilio.jfrog.io` (cloud instance) + +## How to reproduce + +See workflow at: +`segmentio/analytics-swift/.github/workflows/resolve-dependencies.yml` +(branch `poc/binary-xcframework-release-pipeline`) + +Trigger manually via Actions > "Resolve Dependencies (Artifactory OIDC)" > +Run workflow.