Last active
June 26, 2025 14:45
-
-
Save DariuszPorowski/6face0bc5c0a4603ae40da46b9cc7feb to your computer and use it in GitHub Desktop.
[GitHub Workflow] AzDO Artifacts (ENTRA SP with OIDC, UV)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Author: Dariusz Porowski | |
| # GitHub: https://github.com/DariuszPorowski | |
| # License: MIT | |
| # 1. Create SP in Entra https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?view=azure-devops#create-an-application-service-principal | |
| # 2. Add Entra SP to AzDO org https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?view=azure-devops#2-add-and-manage-service-principals-in-an-azure-devops-organization | |
| # SP does not need to be assigned to any Azure Subscription if you do not plan to use it to access Azure Resources but requires extra config on the Azure login step ('allow-no-subscriptions: true') | |
| # 3. Assign Feed permissions in the AzDO Artifacts (via AzDO team, direct assignment not possible so far) https://learn.microsoft.com/en-us/azure/devops/artifacts/feeds/feed-permissions?view=azure-devops | |
| # `Reader` is fine for pulling private packages only | |
| # `Collaborator` if access to public packages is required as well (via upstream sources) | |
| # `Contributor` to publish packages to the Feed | |
| # 4. Setup OIDC auth for GitHub Actions https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-azure | |
| --- | |
| name: AzDO Artifacts (ENTRA SP with OIDC, UV) | |
| on: | |
| push: | |
| branches: | |
| - main | |
| pull_request: | |
| branches: | |
| - main | |
| workflow_dispatch: | |
| # allow one concurrent | |
| concurrency: | |
| group: ${{ format('{0}-{1}-{2}-{3}-{4}', github.workflow, github.event_name, github.ref, github.base_ref, github.head_ref) }} | |
| cancel-in-progress: true | |
| env: | |
| # set DEBUG dynamically, useful when re-run failing workflow | |
| # ref: https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/enabling-debug-logging | |
| DEBUG: ${{ secrets.ACTIONS_RUNNER_DEBUG || vars.ACTIONS_RUNNER_DEBUG || secrets.ACTIONS_STEP_DEBUG || vars.ACTIONS_STEP_DEBUG || false }} | |
| # AzDO Artifacts private feed url (project scope) | |
| # https://pkgs.dev.azure.com/<org_name>/<project_name>/_packaging/<feed_name>/pypi/simple | |
| FEED_URL: "https://pkgs.dev.azure.com/<org_name>/<project_name>/_packaging/<feed_name>/pypi/simple" | |
| # AzDO Artifacts private feed url (organization scope) | |
| # https://pkgs.dev.azure.com/<org_name>/_packaging/<feed_name>/pypi/simple | |
| # FEED_URL: "https://pkgs.dev.azure.com/<org_name>/_packaging/<feed_name>/pypi/simple" | |
| # non-interactive mode for artifacts-keyring | |
| # ref: https://github.com/microsoft/artifacts-keyring#environment-variables | |
| ARTIFACTS_KEYRING_NONINTERACTIVE_MODE: true | |
| # UV specific config | |
| # ref: https://docs.astral.sh/uv/guides/integration/alternative-indexes/#azure-artifacts | |
| UV_INDEX_PRIVATE_REGISTRY_USERNAME: VssSessionToken | |
| jobs: | |
| main: | |
| name: AzDO Artifacts private feed | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: | |
| - ubuntu-latest | |
| - macos-latest | |
| - windows-latest | |
| runs-on: ${{ matrix.os }} | |
| permissions: | |
| id-token: write # required for Azure OIDC auth | |
| contents: read # required for git checkout | |
| steps: | |
| # useful when re-run failing workflow for debugging | |
| - name: Debug | |
| if: ${{ env.DEBUG == 'true' }} | |
| uses: raven-actions/debug@13e7c5b2e0436a1b85276087eba43ec7d46bd955 # v1.1.0 | |
| with: | |
| vars-context: ${{ toJson(vars) }} | |
| needs-context: ${{ toJson(needs) }} | |
| inputs-context: ${{ toJson(inputs) }} | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| # OIDC auth https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-azure | |
| - name: Azure Login | |
| uses: Azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0 | |
| with: | |
| # use if SP with Secret and Subscription | |
| # creds: '{"clientId":"${{ secrets.AZURE_CLIENT_ID }}","clientSecret":"${{ secrets.AZURE_CLIENT_SECRET }}","subscriptionId":"${{ secrets.AZURE_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.AZURE_TENANT_ID }}"}' | |
| # use if SP with Secret and no-Subscription | |
| # creds: '{"clientId":"${{ secrets.AZURE_CLIENT_ID }}","clientSecret":"${{ secrets.AZURE_CLIENT_SECRET }}","tenantId":"${{ secrets.AZURE_TENANT_ID }}"}' | |
| # use if OIDC auth with SP | |
| tenant-id: ${{ secrets.ENTRA_TENANT_ID }} | |
| client-id: ${{ secrets.ENTRA_CLIENT_ID }} | |
| # use if OIDC auth with SP and Subscription | |
| # subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
| # use if OIDC auth with SP and no-Subscription | |
| allow-no-subscriptions: true | |
| # dynamic config - true if Runner is Windows | |
| enable-AzPSSession: ${{ runner.os == 'Windows' || false }} | |
| # ref: https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?view=azure-devops#q-can-i-use-a-service-principal-or-managed-identity-with-azure-cli | |
| # resource 499b84ac-1321-427f-aa17-267ca6975798 safe to hard-code, refers to the Azure DevOps resource (ref: https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/manage-personal-access-tokens-via-api?view=azure-devops) | |
| - name: Get AzDO access token (Linux/macOS) | |
| if: ${{ runner.os != 'Windows' }} | |
| run: | | |
| echo "Obtain access token for Service Connection identity..." | |
| access_token=$(az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query accessToken --output tsv) | |
| echo "::add-mask::${access_token}" | |
| echo "ACCESS_TOKEN=${access_token}" >> "${GITHUB_ENV}" | |
| shell: bash | |
| # ref: https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?view=azure-devops#q-can-i-use-a-service-principal-or-managed-identity-with-azure-cli | |
| # resource 499b84ac-1321-427f-aa17-267ca6975798 safe to hard-code, refers to the Azure DevOps resource (ref: https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/manage-personal-access-tokens-via-api?view=azure-devops) | |
| - name: Get AzDO access token (Windows) | |
| if: ${{ runner.os == 'Windows' }} | |
| run: | | |
| Write-Output "Obtain access token for Service Connection identity..." | |
| $accessToken = $(az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query accessToken --output tsv) | |
| Write-Output "::add-mask::${accessToken}" | |
| Write-Output "ACCESS_TOKEN=${accessToken}" >> $env:GITHUB_ENV | |
| shell: pwsh | |
| # required for: artifacts-keyring | |
| # ref: https://github.com/microsoft/artifacts-keyring#artifacts-keyring | |
| - name: Setup Python | |
| uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 | |
| with: | |
| python-version: "3.13" | |
| - name: Setup UV | |
| uses: astral-sh/setup-uv@445689ea25e0de0a23313031f5fe577c74ae45a1 # v6.3.0 | |
| # required for: Azure Artifacts Credential Provider | |
| # https://github.com/microsoft/artifacts-credprovider | |
| - name: Setup DotNet | |
| uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 | |
| with: | |
| dotnet-version: 8 | |
| - name: Set ARTIFACTS_CREDENTIAL_PROVIDER_RID (Windows) | |
| if: ${{ runner.os == 'Windows' }} | |
| run: Write-Output "ARTIFACTS_CREDENTIAL_PROVIDER_RID=win-x64" >> $env:GITHUB_ENV | |
| shell: pwsh | |
| - name: Set ARTIFACTS_CREDENTIAL_PROVIDER_RID (Linux/macOS) | |
| if: ${{ runner.os != 'Windows' }} | |
| run: | | |
| # check OS | |
| if [ "$(uname -s)" = "Linux" ]; then | |
| os="linux" | |
| elif [ "$(uname -s)" = "Darwin" ]; then | |
| os="osx" | |
| else | |
| echo "Unsupported OS: $(uname -s)" | |
| exit 1 | |
| fi | |
| # check arch | |
| if [ "$(uname -m)" = "x86_64" ]; then | |
| arch="x64" | |
| elif [ "$(uname -m)" = "arm64" ]; then | |
| arch="arm64" | |
| else | |
| echo "Unsupported architecture: $(uname -m)" | |
| exit 1 | |
| fi | |
| echo "ARTIFACTS_CREDENTIAL_PROVIDER_RID=${os}-${arch}" >> "${GITHUB_ENV}" | |
| shell: bash | |
| # ref: https://github.com/microsoft/artifacts-keyring#artifacts-keyring | |
| - name: Install and setup AzDO Artifacts cred provider (Linux/macOS) | |
| if: ${{ runner.os != 'Windows' }} | |
| run: | | |
| # setup feed credentials | |
| export VSS_NUGET_EXTERNAL_FEED_ENDPOINTS='{"endpointCredentials":[{"endpoint":"'${FEED_URL}'","username":"VssSessionToken","password":"'${ACCESS_TOKEN}'"}]}' | |
| echo "VSS_NUGET_EXTERNAL_FEED_ENDPOINTS=${VSS_NUGET_EXTERNAL_FEED_ENDPOINTS}" >> "${GITHUB_ENV}" | |
| # install Azure Artifacts Credential Provider | |
| sh -c "$(curl -fsSL https://aka.ms/install-artifacts-credprovider.sh)" | |
| shell: bash | |
| # ref: https://github.com/microsoft/artifacts-credprovider | |
| - name: Install and setup AzDO Artifacts cred provider (Windows) | |
| if: ${{ runner.os == 'Windows' }} | |
| run: | | |
| # setup feed credentials | |
| $env:VSS_NUGET_EXTERNAL_FEED_ENDPOINTS = "{`"endpointCredentials`":[{`"endpoint`":`"${env:FEED_URL}`",`"username`":`"VssSessionToken`",`"password`":`"${env:ACCESS_TOKEN}`"}]}" | |
| Write-Output "VSS_NUGET_EXTERNAL_FEED_ENDPOINTS=${env:VSS_NUGET_EXTERNAL_FEED_ENDPOINTS}" >> $env:GITHUB_ENV | |
| # install Azure Artifacts Credential Provider | |
| iex "& { $(irm https://aka.ms/install-artifacts-credprovider.ps1) } -InstallNet8" | |
| shell: pwsh | |
| # ref: https://github.com/microsoft/artifacts-keyring#artifacts-keyring | |
| - name: Install keyring | |
| run: uv tool install keyring --with artifacts-keyring | |
| - name: Set UV_INDEX_URL (Windows) | |
| if: ${{ runner.os == 'Windows' }} | |
| run: | | |
| $uvIndexUrl = $env:FEED_URL -replace "https://pkgs.dev.azure.com", "https://$($env:UV_INDEX_PRIVATE_REGISTRY_USERNAME)@pkgs.dev.azure.com" | |
| Write-Output "UV_INDEX_URL=$uvIndexUrl" >> $env:GITHUB_ENV | |
| shell: pwsh | |
| - name: Set UV_INDEX_URL (Linux/macOS) | |
| if: ${{ runner.os != 'Windows' }} | |
| run: | | |
| UV_INDEX_URL=$(echo "${FEED_URL}" | sed "s|https://pkgs.dev.azure.com|https://${UV_INDEX_PRIVATE_REGISTRY_USERNAME}@pkgs.dev.azure.com|") | |
| echo "UV_INDEX_URL=${UV_INDEX_URL}" >> "${GITHUB_ENV}" | |
| shell: bash | |
| # ref: https://docs.astral.sh/uv/guides/integration/alternative-indexes/#authenticate-with-keyring-and-artifacts-keyring | |
| - name: Install deps from AzDO Artifacts Feed | |
| run: uv sync --all-extras --all-groups | |
| env: | |
| UV_KEYRING_PROVIDER: subprocess | |
| # ref: https://github.com/Azure/login#az-logout-and-security-hardening | |
| - name: Azure Logout | |
| if: always() | |
| run: | | |
| az logout | |
| az cache purge | |
| az account clear |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment