Skip to content

Instantly share code, notes, and snippets.

@DariuszPorowski
Last active June 26, 2025 14:34
Show Gist options
  • Select an option

  • Save DariuszPorowski/3b43aeffd1cc8a5a7ade6a0188bfe768 to your computer and use it in GitHub Desktop.

Select an option

Save DariuszPorowski/3b43aeffd1cc8a5a7ade6a0188bfe768 to your computer and use it in GitHub Desktop.
[GitHub Workflow] AzDO Artifacts (ENTRA SP with OIDC, PIP)
# 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
# 5. Read notes at the bottom of the workflow with extra python3 specific config
---
name: AzDO Artifacts (ENTRA SP with OIDC, PIP)
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
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"
# 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 its linux or macos
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 its x64 or arm64 for macos
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: python3 -m pip install --upgrade keyring artifacts-keyring --index-url https://pypi.org/simple
# Publish python3 packages
# .pypirc ref: https://packaging.python.org/en/latest/specifications/pypirc
# AzDO Artifacts https://learn.microsoft.com/en-us/azure/devops/artifacts/quickstarts/python-cli
# Consume python3 packages
# pip.ini (Windows) / pip.conf (Linux/macOS) ref: https://pip.pypa.io/en/stable/topics/configuration/
# index-url= force to use private feed for public pip packages (via upstream source)
# extra-index-url= use private feed only for private packages, public pull from public pypi
# AzDO Artifacts https://learn.microsoft.com/en-us/azure/devops/artifacts/quickstarts/install-python-packages
- name: Prepare pip.conf (Linux/macOS)
if: ${{ runner.os != 'Windows' }}
run: |
if [ -d ".venv" ]; then
VENV_PATH=".venv"
elif [ -d "venv" ]; then
VENV_PATH="venv"
else
VENV_PATH="."
fi
echo "[global]" > "${VENV_PATH}/pip.conf"
echo "index-url=${FEED_URL}" >> "${VENV_PATH}/pip.conf"
shell: bash
- name: Prepare pip.ini (Windows)
if: ${{ runner.os == 'Windows' }}
run: |
if (Test-Path -Path ".venv") {
$VENV_PATH = ".venv"
} elseif (Test-Path -Path "venv") {
$VENV_PATH = "venv"
} else {
$VENV_PATH = "."
}
# Windows
Write-Output "[global]" > "${VENV_PATH}/pip.ini"
Write-Output "index-url=${FEED_URL}" >> "${VENV_PATH}/pip.ini"
shell: pwsh
# ref: https://learn.microsoft.com/en-us/azure/devops/artifacts/quickstarts/python-cli?view=azure-devops#consume-python-packages
- name: Install deps from AzDO Artifacts Feed
run: python3 -m pip install -r requirements.txt
env:
PIP_INDEX_URL: ${{ env.FEED_URL }}
# 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