Skip to content

Added e2e nuget example for Azure DevOps #26

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions .azuredevops/create-pull-requests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#!/bin/bash

# Adapted from https://github.com/dependabot/example-cli-usage/blob/main/create.sh

# Expected Environment Variables:
#### AZURE_DEVOPS_EXT_PAT: The PAT token for the Azure DevOps organization.
#### PROJECT_PATH: The path to the repository, relative to Azure DevOps.

# This script takes a jsonl file as input which is the stdout of a Dependabot CLI run.
# It takes the `type: create_pull_request` events and creates a pull request for each of them
# by using git commands.

# Note at this time there is minimal error handling.
set -euo pipefail

if [ $# -ne 1 ]; then
echo "Usage: $0 <result.jsonl>"
exit 1
fi

# This script takes the .jsonl file output from the Dependabout CLI as its only param.
INPUT="$1"

# DEBUG
echo "AZURE_DEVOPS_EXT_PAT: $AZURE_DEVOPS_EXT_PAT"
echo "PROJECT_PATH: $PROJECT_PATH"

# Function to check if a string is valid Base64
is_base64() {
local input="$1"
# Remove any whitespace and newlines
input=$(echo "$input" | tr -d ' \t\n\r')
# Check if the string contains only Base64 characters and has proper length
if [[ "$input" =~ ^[A-Za-z0-9+/]*={0,2}$ ]] && [[ $(( ${#input} % 4 )) -eq 0 ]]; then
return 0
else
return 1
fi
}

# Configure Git Credentials
git config --global user.email "[email protected]"
git config --global user.name "Dependabot Standalone"
git config --global advice.detachedHead false

# Configure the credential helper to store the PAT token in the git-credentials file.
git config --global credential.helper store

# Configure the git-credentials to use the PAT token from ADO.
echo "https://azure:${AZURE_DEVOPS_EXT_PAT}@dev.azure.com" > ~/.git-credentials
git config --global url."https://azure:${AZURE_DEVOPS_EXT_PAT}@dev.azure.com/".insteadOf "https://dev.azure.com/"

# Parse each create_pull_request event
jq -c 'select(.type == "create_pull_request")' "$INPUT" | while read -r event; do
# Extract fields
BASE_SHA=$(echo "$event" | jq -r '.data."base-commit-sha"')
PR_TITLE=$(echo "$event" | jq -r '.data."pr-title"')
PR_BODY=$(echo "$event" | jq -r '.data."pr-body"')
COMMIT_MSG=$(echo "$event" | jq -r '.data."commit-message"')
BRANCH_NAME="dependabot/$(echo -n "$COMMIT_MSG" | sha1sum | awk '{print $1}')"

echo "Processing PR: $PR_TITLE"
echo " Base SHA: $BASE_SHA"
echo " Branch: $BRANCH_NAME"

# Set the remote URL to the repository using the PAT.
git remote set-url origin https://${AZURE_DEVOPS_EXT_PAT}@dev.azure.com/${PROJECT_PATH}

# Create and checkout new branch from base commit
git fetch origin
git checkout "$BASE_SHA"
git switch -c "$BRANCH_NAME"

# Apply file changes
echo "$event" | jq -c '.data."updated-dependency-files"[]' | while read -r file; do
# Construct file path more safely to ensure it's relative
DIRECTORY=$(echo "$file" | jq -r '.directory // ""')
FILENAME=$(echo "$file" | jq -r '.name')

# Build relative path, handling empty directory case
if [ -z "$DIRECTORY" ] || [ "$DIRECTORY" = "." ] || [ "$DIRECTORY" = "/" ]; then
FILE_PATH="$FILENAME"
else
# Remove leading slash if present and ensure relative path
DIRECTORY=$(echo "$DIRECTORY" | sed 's#^/##')
FILE_PATH="$DIRECTORY/$FILENAME"
fi

DELETED=$(echo "$file" | jq -r '.deleted')
if [ "$DELETED" = "true" ]; then
git rm -f "$FILE_PATH" || true
else
mkdir -p "$(dirname "$FILE_PATH")"

# Get the content
CONTENT=$(echo "$file" | jq -r '.content')

# Check if content is Base64 encoded
# Note - this appears to be a necessary check - `Directory.Packages.props` are written out as a Base64 string.
# Other projects using `packages.config` are written out in their original format.
if is_base64 "$CONTENT"; then
# Decode Base64 content before writing to file
echo "$CONTENT" | base64 -d > "$FILE_PATH"
else
# Content is already in plain text, write directly
echo "$CONTENT" > "$FILE_PATH"
fi

git add "$FILE_PATH"
fi
done

git commit -m "$COMMIT_MSG"
git push origin "$BRANCH_NAME"

# Create PR using Azure CLI (az) - adjust options as desired.
# https://learn.microsoft.com/en-us/cli/azure/repos/pr?view=azure-cli-latest#az-repos-pr-create
az repos pr create \
--title "$PR_TITLE" --description "$PR_BODY" \
--target-branch "main" --source-branch "$BRANCH_NAME" \
--labels dependencies --auto-complete true \
--delete-source-branch true --squash true || true

# Return to main branch for next PR
git checkout main
done
37 changes: 37 additions & 0 deletions .azuredevops/dependabot/nuget.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
job:
package-manager: "nuget"
allowed-updates:
- update-type: all
dependency-groups:
- name: MSNet
applies-to: all
rules:
patterns:
- "Microsoft.*"
- "System.*"
- name: Nuget
applies-to: all
rules:
patterns:
- "*"
experiments:
nuget_generate_simple_pr_body: true
nuget_native_updater: true
nuget_use_direct_discovery: true
nuget_use_new_file_updater: true
ignore-conditions:
- dependency-name: Newtonsoft.Json
commit-message-options:
prefix: "dependabot"
source:
provider: azure
repo: $PROJECT_PATH
directory: '/'
credentials:
- type: git_source
host: dev.azure.com
username: vsts
password: $LOCAL_GITHUB_ACCESS_TOKEN
- type: nuget-feed
url: https://pkgs.dev.azure.com/{MY_ORGANIZATION_NAME}/_packaging/{MY_NUGET_FEED_NAME}/nuget/v3/index.json
token: $LOCAL_AZURE_ACCESS_TOKEN
175 changes: 175 additions & 0 deletions .azuredevops/pipelines/example.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
parameters:
# Note: The presence of the 3 parameters below provides a mechanism to centralize this pipeline such that it can be run for multiple repositories.
# Parallelization has not been tested using this approach - but it should be easy enough to do.
# See here for more information: https://learn.microsoft.com/en-us/azure/machine-learning/how-to-use-parallel-job-in-pipeline?view=azureml-api-2&tabs=cliv2

# By default, this pipeline is configured to run for a single repository.

# OPTIONAL: Name of the repository. Automatically set to ADO repository name if not provided.
- name: repositoryName
type: string
default: $(Build.Repository.Name)

# OPTIONAL: Name of the Azure DevOps project. Automatically set to ADO project name if not provided.
- name: azdoProjectName
type: string
default: $(System.TeamProject)

# OPTIONAL: Path to the repository, relative to Azure DevOps.
# e.g. invoicecloud/Src/_git/Repository.Name
- name: projectPath
type: string
default: ''

# OPTIONAL: Path to the Dependabot configuration file. Relative to the repository root.
- name: dependabotConfigFile
type: string
default: "$(Build.SourcesDirectory)/.azuredevops/dependabot-config.yaml"

# OPTIONAL: Version of GoLang to install.
# See here for other versions: https://go.dev/dl/
- name: goVersion
type: string
default: '1.24.5'
values:
- '1.24.5'
- '1.23.11'
- '1.25rc2'

# OPTIONAL: Version of Dependabot CLI to install - defaults to latest.
# See here for other versions: https://github.com/dependabot/cli/releases
- name: dependabotCliVersion
type: string
default: 'latest'


trigger: none

schedules:
- cron: "0 0 * * 0" # Weekly, Sunday Night, Midnight UTC
always: true # Run even if there have been no code changes.
branches:
include:
- main # The branch to run the schedule on.
batch: true
displayName: "Weekly Dependency Update"

variables:
- name: System.Secrets
value: true

# Azure DevOps Repository Path
- name: PROJECT_PATH
${{ if eq(parameters.projectPath, '') }}:
value: 'MY_AZDO_ORGANIZATION_NAME/${{ parameters.azdoProjectName }}/_git/${{ parameters.repositoryName }}'
${{ else }}:
value: ${{ parameters.projectPath }}



stages:
- stage: BuildDependabot
jobs:
- job: RunDependabot
pool:
vmImage: 'ubuntu-latest'
steps:
- checkout: self
persistCredentials: true

# Add .NET
# Parameterize version if desired.
- task: UseDotNet@2
inputs:
version: '8.0.x'

# Install GoLang
- script: |
# Install GoLang
wget https://go.dev/dl/go${{ parameters.goVersion }}.src.tar.gz
sudo tar -C /usr/local -xzf ${{ parameters.goVersion }}.src.tar.gz

# Add GoLang to PATH
echo "export PATH=/usr/local/go/bin:${PATH}" | sudo tee -a $HOME/.profile
source $HOME/.profile

go version
displayName: Install GoLang

# Install Dependabot CLI
- script: |
# Install Dependabot CLI
go install github.com/dependabot/cli/cmd/dependabot@${{ parameters.dependabotCliVersion }}
displayName: Install Dependabot CLI

# Run Dependabot
- script: |
set -euo pipefail

# Substitute PROJECT_PATH var in the config.
# This doesn't appear to be automatically interpolated in Azure DevOps.
sed -i 's/\$PROJECT_PATH/${PROJECT_PATH}/g' ${{ parameters.dependabotConfigFile }}

# Print the updated config file.
echo "Using Dependabot Configuration File:"
cat ${{ parameters.dependabotConfigFile }}

# Set the GO /bin path & cd into it.
GO_PATH=$(go env | grep GOPATH | awk -F'=' '{print $2}' | tr -d "'")
cd $GO_PATH/bin

echo "\n dependabot update \
-f ${{ parameters.dependabotConfigFile }} \
--timeout 20m >> $(Pipeline.Workspace)/dependabot_result.jsonl || true"

./dependabot update \
-f ${{ parameters.dependabotConfigFile }} \
--timeout 20m >> $(Pipeline.Workspace)/dependabot_result.jsonl || true

echo "Result:"
cat $(Pipeline.Workspace)/dependabot_result.jsonl
displayName: Run Dependabot
env:
LOCAL_AZURE_ACCESS_TOKEN: $(System.AccessToken)
LOCAL_GITHUB_ACCESS_TOKEN: $(System.AccessToken)

# Publish Dependabot Results
- task: PublishPipelineArtifact@1
displayName: Publish Dependabot Results
inputs:
targetPath: '$(Pipeline.Workspace)/dependabot_result.jsonl'
publishLocation: 'pipeline'
artifactName: 'dependabot_result'

- stage: CreatePullRequests
jobs:
- job: CreatePullRequests
pool:
vmImage: 'ubuntu-latest'
steps:
# Download Dependabot Results
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'dependabot_result'
targetPath: $(Build.ArtifactStagingDirectory)

# Install jq - for parsing JSON.
- script: |
# Install jq
sudo apt-get update
sudo apt-get install -y jq
displayName: Install 'jq'

# Create Pull Requests
- task: Bash@3
displayName: Create Pull Requests
inputs:
targetType: 'filePath'
filePath: "./create-pull-requests.sh"
arguments: >
$(Build.ArtifactStagingDirectory)/dependabot_result.jsonl
workingDirectory: $(Build.SourcesDirectory)/$(Build.Repository.Name)
env:
AZURE_DEVOPS_EXT_PAT: $(System.AccessToken)
PROJECT_PATH: $(PROJECT_PATH)