Skip to content
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

[repo] Automate release process #1841

Merged
Merged
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
179 changes: 179 additions & 0 deletions .github/workflows/prepare-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
name: Prepare for a release

on:
workflow_dispatch:
inputs:
component:
type: choice
options:
- OpenTelemetry.Exporter.Geneva
- OpenTelemetry.Exporter.InfluxDB
- OpenTelemetry.Exporter.Instana
- OpenTelemetry.Exporter.OneCollector
- OpenTelemetry.Exporter.Stackdriver
- OpenTelemetry.Extensions
- OpenTelemetry.Extensions.AWS
- OpenTelemetry.Extensions.Enrichment
- OpenTelemetry.Instrumentation.AspNet
- OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule
Comment on lines +17 to +18
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kid of misleading. This packages are released always together.

Similar thing for OpenTelemetry.PersistentStorage.*

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. Really this list should be the tag prefixes not components. I decided to go with components though because a) in most cases that is what users will be looking for (probably?) and b) it makes it easy to keep this list in sync with the other places it is mirrored (issue templates). I was hoping there was a way in yaml to pull in a list of items from another file so we could have a single source of truth for the set of known components but it doesn't seem possible 😢

How it works is you pick a component. The workflow will look for a csproj for that component. Then it will find the MinVerTagPrefix for that project. The release is then performed for that MinVerTagPrefix. So when it comes to OpenTelemetry.Instrumentation.AspNet* and OpenTelemetry.PersistentStorage* you have to kick off the job for one of the components in the set (doesn't matter which) and the job will release both. It is a bit odd, but I think the benefits outweigh the quirk(s)?

Let me respond to some of the other comments on here...

Based on what I see, the whole process has to be either fully handled by new pipeline or fully in the old way.

Not true! When you kick off the job a PR will be opened to update CHANGELOGs and public api files (for stable releases). Someone has to merge that. Then the bot will ask you if you want it to create the tag. If you choose to not have the bot create the tag, you can do everything the manual way. Probably easier to let the bot do it, but it isn't forced 😄

Allow created end-users requests as described in

Agree with this. To kick off this new job you need to be a collaborator in the repo (write access). This will help maintainers but it won't be widely useful. What I am thinking is add an issue template for "Request release" end users may use to request a release be done. I'm hoping the flow will be users select a component, and then the bot can look for code owners for the selected component to approve before kicking off the job. That would give us a nice flow/mechanism. Going to look at that as a follow-up.

Bulk releases

I think this is already covered by: https://github.com/open-telemetry/opentelemetry-dotnet-contrib/actions/workflows/core-version-update.yml

Maintainers can kick that workflow off anytime a core release happens and it will generate the PR to bump all the components. My goal is to have the main repo automatically call this but that isn't in place yet.

- OpenTelemetry.Instrumentation.AspNetCore
- OpenTelemetry.Instrumentation.AWS
- OpenTelemetry.Instrumentation.AWSLambda
- OpenTelemetry.Instrumentation.Cassandra
- OpenTelemetry.Instrumentation.ElasticsearchClient
- OpenTelemetry.Instrumentation.EntityFrameworkCore
- OpenTelemetry.Instrumentation.EventCounters
- OpenTelemetry.Instrumentation.GrpcCore
- OpenTelemetry.Instrumentation.GrpcNetClient
- OpenTelemetry.Instrumentation.Hangfire
- OpenTelemetry.Instrumentation.Http
- OpenTelemetry.Instrumentation.MassTransit
- OpenTelemetry.Instrumentation.MySqlData
- OpenTelemetry.Instrumentation.Owin
- OpenTelemetry.Instrumentation.Process
- OpenTelemetry.Instrumentation.Quartz
- OpenTelemetry.Instrumentation.Runtime
- OpenTelemetry.Instrumentation.SqlClient
- OpenTelemetry.Instrumentation.StackExchangeRedis
- OpenTelemetry.Instrumentation.Wcf
- OpenTelemetry.PersistentStorage.Abstractions
- OpenTelemetry.PersistentStorage.FileSystem
- OpenTelemetry.Resources.AWS
- OpenTelemetry.Resources.Azure
- OpenTelemetry.Resources.Container
- OpenTelemetry.Resources.Gcp
- OpenTelemetry.Resources.Host
- OpenTelemetry.Resources.Process
- OpenTelemetry.Resources.ProcessRuntime
Comment on lines +41 to +47
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just FYI I went with OpenTelemetry.Resources for all of these here. Seems like we'll be there soon. For ones that aren't updated, they can still be released doing everything manually the old way. Or we could just use the existing names and fix them up as we go. I thought this might save a bit of work.

- OpenTelemetry.Sampler.AWS
- OpenTelemetry.SemanticConventions
description: 'Release component'
required: true
version:
type: string
description: 'Release version'
required: true

pull_request:
types:
- closed

issue_comment:
types:
- created

permissions:
contents: write
pull-requests: write

jobs:
prepare-release-pr:
if: github.event_name == 'workflow_dispatch'

runs-on: windows-latest

steps:
- name: check out code
uses: actions/checkout@v4

- name: Create GitHub Pull Request to prepare release
shell: pwsh
run: |
Import-Module .\build\scripts\prepare-release.psm1

CreatePullRequestToUpdateChangelogsAndPublicApis `
-component '${{ inputs.component }}' `
-version '${{ inputs.version }}' `
-targetBranch '${{ github.ref_name }}'
env:
GH_TOKEN: ${{ github.token }}

lock-pr-and-post-notice-to-create-release-tag:
if: |
github.event_name == 'pull_request'
&& github.event.action == 'closed'
&& github.event.pull_request.user.login == 'github-actions[bot]'
&& github.event.pull_request.merged == true
&& startsWith(github.event.pull_request.title, '[repo] Prepare release ')

runs-on: windows-latest

steps:
- name: check out code
uses: actions/checkout@v4

- name: Lock GitHub Pull Request to prepare release
shell: pwsh
run: |
Import-Module .\build\scripts\prepare-release.psm1

LockPullRequestAndPostNoticeToCreateReleaseTag `
-pullRequestNumber '${{ github.event.pull_request.number }}'
env:
GH_TOKEN: ${{ github.token }}

create-release-tag-unlock-pr-post-notice:
if: |
github.event_name == 'issue_comment'
&& github.event.issue.pull_request
&& github.event.issue.locked == true
&& contains(github.event.comment.body, '/CreateReleaseTag')
&& startsWith(github.event.issue.title, '[repo] Prepare release ')
&& github.event.issue.pull_request.merged_at

runs-on: windows-latest

outputs:
tag: ${{ steps.create-tag.outputs.tag }}

steps:
- name: check out code
uses: actions/checkout@v4
with:
# Note: By default GitHub only fetches 1 commit which fails the git tag operation below
fetch-depth: 0

- name: Create release tag
id: create-tag
shell: pwsh
run: |
Import-Module .\build\scripts\prepare-release.psm1

$tag = ''

CreateReleaseTag `
-pullRequestNumber '${{ github.event.issue.number }}' `
-actionRunId '${{ github.run_id }}' `
-tag ([ref]$tag)

echo "tag=$tag" >> $env:GITHUB_OUTPUT
env:
GH_TOKEN: ${{ github.token }}

invoke-package-workflow:
needs: create-release-tag-unlock-pr-post-notice
uses: ./.github/workflows/publish-packages.yml
with:
tag: ${{ needs.create-release-tag-unlock-pr-post-notice.outputs.tag }}
secrets: inherit

post-packages-ready-notice:
needs:
- create-release-tag-unlock-pr-post-notice
- invoke-package-workflow
runs-on: windows-latest
steps:
- name: check out code
uses: actions/checkout@v4

- name: Post notice when packages are ready
shell: pwsh
run: |
Import-Module .\build\scripts\prepare-release.psm1

PostPackagesReadyNotice `
-pullRequestNumber '${{ github.event.issue.number }}' `
-tag '${{ needs.create-release-tag-unlock-pr-post-notice.outputs.tag }}' `
-packagesUrl '${{ needs.invoke-package-workflow.outputs.artifact-url }}'
env:
GH_TOKEN: ${{ github.token }}
203 changes: 203 additions & 0 deletions build/scripts/prepare-release.psm1
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
$gitHubBotUserName="github-actions[bot]"
$gitHubBotEmail="41898282+github-actions[bot]@users.noreply.github.com"

$repoViewResponse = gh repo view --json nameWithOwner | ConvertFrom-Json

$gitRepository = $repoViewResponse.nameWithOwner

function CreatePullRequestToUpdateChangelogsAndPublicApis {
param(
[Parameter(Mandatory=$true)][string]$component,
[Parameter(Mandatory=$true)][string]$version,
[Parameter()][string]$gitUserName=$gitHubBotUserName,
[Parameter()][string]$gitUserEmail=$gitHubBotEmail,
[Parameter()][string]$targetBranch="main"
)

$projectContent = Get-Content -Path src/$component/$component.csproj

$match = [regex]::Match($projectContent, '<MinVerTagPrefix>(.*)<\/MinVerTagPrefix>')
if ($match.Success -eq $false)
{
throw 'Could not parse MinVerTagPrefix from project file'
}

$minVerTagPrefix = $match.Groups[1].Value
$tag="${minVerTagPrefix}${version}"
$branch="release/prepare-${tag}-release"

git config user.name $gitUserName
git config user.email $gitUserEmail

git switch --create $branch 2>&1 | % ToString
if ($LASTEXITCODE -gt 0)
{
throw 'git switch failure'
}

$body =
@"
Note: This PR was opened automatically by the [prepare release workflow](https://github.com/$gitRepository/actions/workflows/prepare-release.yml).

## Changes

* CHANGELOG files updated for projects being released.
"@

# Update CHANGELOGs
& ./build/scripts/update-changelogs.ps1 -minVerTagPrefix $minVerTagPrefix -version $version

# Update publicApi files for stable releases
if ($version -notlike "*-alpha*" -and $version -notlike "*-beta*" -and $version -notlike "*-rc*")
{
& ./build/scripts/finalize-publicapi.ps1 -minVerTagPrefix $minVerTagPrefix

$body += "`r`n* Public API files updated for projects being released (only performed for stable releases)."
}

git commit -a -m "Prepare repo to release $tag." 2>&1 | % ToString
if ($LASTEXITCODE -gt 0)
{
throw 'git commit failure'
}

git push -u origin $branch 2>&1 | % ToString
if ($LASTEXITCODE -gt 0)
{
throw 'git push failure'
}

gh pr create `
--title "[repo] Prepare release $tag" `
--body $body `
--base $targetBranch `
--head $branch `
--label infra
}

Export-ModuleMember -Function CreatePullRequestToUpdateChangelogsAndPublicApis

function LockPullRequestAndPostNoticeToCreateReleaseTag {
param(
[Parameter(Mandatory=$true)][string]$pullRequestNumber,
[Parameter()][string]$gitUserName=$gitHubBotUserName,
[Parameter()][string]$gitUserEmail=$gitHubBotEmail
)

git config user.name $gitUserName
git config user.email $gitUserEmail

$prViewResponse = gh pr view $pullRequestNumber --json mergeCommit,author,title | ConvertFrom-Json

if ($prViewResponse.author.is_bot -eq $false -or $prViewResponse.author.login -ne 'app/github-actions')
{
throw 'PR author was unexpected'
}

$match = [regex]::Match($prViewResponse.title, '^\[repo\] Prepare release (.*)$')
if ($match.Success -eq $false)
{
throw 'Could not parse tag from PR title'
}

$tag = $match.Groups[1].Value

$commit = $prViewResponse.mergeCommit.oid
if ([string]::IsNullOrEmpty($commit) -eq $true)
{
throw 'Could not find merge commit'
}

$body =
@"
I noticed this PR was merged.

Post a comment with "/CreateReleaseTag" in the body if you would like me to create the release tag ``$tag`` for [the merge commit](https://github.com/$gitRepository/commit/$commit) and then trigger the package workflow.
"@

gh pr comment $pullRequestNumber --body $body

gh pr lock $pullRequestNumber
}

Export-ModuleMember -Function LockPullRequestAndPostNoticeToCreateReleaseTag

function CreateReleaseTag {
param(
[Parameter(Mandatory=$true)][string]$pullRequestNumber,
[Parameter(Mandatory=$true)][string]$actionRunId,
[Parameter()][string]$gitUserName=$gitHubBotUserName,
[Parameter()][string]$gitUserEmail=$gitHubBotEmail,
[Parameter()][ref]$tag
)

git config user.name $gitUserName
git config user.email $gitUserEmail

$prViewResponse = gh pr view $pullRequestNumber --json mergeCommit,author,title | ConvertFrom-Json

if ($prViewResponse.author.is_bot -eq $false -or $prViewResponse.author.login -ne 'app/github-actions')
{
throw 'PR author was unexpected'
}

$match = [regex]::Match($prViewResponse.title, '^\[repo\] Prepare release (.*)$')
if ($match.Success -eq $false)
{
throw 'Could not parse tag from PR title'
}

$tagValue = $match.Groups[1].Value

$commit = $prViewResponse.mergeCommit.oid
if ([string]::IsNullOrEmpty($commit) -eq $true)
{
throw 'Could not find merge commit'
}

git tag -a $tagValue -m "$tagValue" $commit 2>&1 | % ToString
if ($LASTEXITCODE -gt 0)
{
throw 'git tag failure'
}

git push origin $tagValue 2>&1 | % ToString
if ($LASTEXITCODE -gt 0)
{
throw 'git push failure'
}

gh pr unlock $pullRequestNumber

$body =
@"
I just pushed the [$tagValue](https://github.com/$gitRepository/releases/tag/$tagValue) tag.

The [package workflow](https://github.com/$gitRepository/actions/runs/$actionRunId) should begin momentarily.
"@

gh pr comment $pullRequestNumber --body $body

$tag.value = $tagValue
}

Export-ModuleMember -Function CreateReleaseTag

function PostPackagesReadyNotice {
param(
[Parameter(Mandatory=$true)][string]$pullRequestNumber,
[Parameter(Mandatory=$true)][string]$tag,
[Parameter(Mandatory=$true)][string]$packagesUrl
)

$body =
@"
The [packages]($packagesUrl) for [$tag](https://github.com/$gitRepository/releases/tag/$tag) should be available on NuGet shortly.

Have a nice day!
"@

gh pr comment $pullRequestNumber --body $body
}

Export-ModuleMember -Function PostPackagesReadyNotice
Loading
Loading