Skip to content
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
4 changes: 4 additions & 0 deletions .github/config/pr-autoflow.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"AUTO_MERGE_PACKAGE_WILDCARD_EXPRESSIONS": "[\"Endjin.*\",\"Corvus.*\"]",
"AUTO_RELEASE_PACKAGE_WILDCARD_EXPRESSIONS": "[\"Corvus.AzureFunctionsKeepAlive\",\"Corvus.Configuration\",\"Corvus.ContentHandling\",\"Corvus.Deployment\",\"Corvus.Deployment.Dataverse\",\"Corvus.DotLiquidAsync\",\"Corvus.EventStore\",\"Corvus.Extensions\",\"Corvus.Extensions.CosmosDb\",\"Corvus.Extensions.Newtonsoft.Json\",\"Corvus.Extensions.System.Text.Json\",\"Corvus.Identity\",\"Corvus.JsonSchema\",\"Corvus.Leasing\",\"Corvus.Monitoring\",\"Corvus.Python\",\"Corvus.Retry\",\"Corvus.Storage\",\"Corvus.Tenancy\"]"
}
15 changes: 15 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
version: 2
updates:
- package-ecosystem: nuget
directory: /Solutions
schedule:
interval: daily
open-pull-requests-limit: 10
groups:
microsoft-identity:
patterns:
- Microsoft.Identity.*
microsoft-extensions:
patterns:
- Microsoft.Extensions.*

270 changes: 270 additions & 0 deletions .github/workflows/auto_release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
name: auto_release
on:
pull_request:
types: [closed]

jobs:
lookup_default_branch:
runs-on: ubuntu-latest
outputs:
branch_name: ${{ steps.lookup_default_branch.outputs.result }}
head_commit: ${{ steps.lookup_default_branch_head.outputs.result }}
steps:
- name: Lookup default branch name
id: lookup_default_branch
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
retries: 6 # final retry should wait 64 seconds
retry-exempt-status-codes: 400,401,404,422 # GH will raise rate limits with 403 & 429 status codes
result-encoding: string
script: |
const repo = await github.rest.repos.get({
owner: context.payload.repository.owner.login,
repo: context.payload.repository.name
});
return repo.data.default_branch
- name: Display default_branch_name
run: |
echo "default_branch_name : ${{ steps.lookup_default_branch.outputs.result }}"

- name: Lookup HEAD commit on default branch
id: lookup_default_branch_head
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
retries: 6 # final retry should wait 64 seconds
retry-exempt-status-codes: 400,401,404,422 # GH will raise rate limits with 403 & 429 status codes
result-encoding: string
script: |
const branch = await github.rest.repos.getBranch({
owner: context.payload.repository.owner.login,
repo: context.payload.repository.name,
branch: '${{ steps.lookup_default_branch.outputs.result }}'
});
return branch.data.commit.sha
- name: Display default_branch_name_head
run: |
echo "default_branch_head_commit : ${{ steps.lookup_default_branch_head.outputs.result }}"

check_for_norelease_label:
runs-on: ubuntu-latest
outputs:
no_release: ${{ steps.check_for_norelease_label.outputs.result }}
steps:
- name: Check for 'no_release' label on PR
id: check_for_norelease_label
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
retries: 6 # final retry should wait 64 seconds
retry-exempt-status-codes: 400,401,404,422 # GH will raise rate limits with 403 & 429 status codes
script: |
const labels = await github.rest.issues.listLabelsOnIssue({
owner: context.payload.repository.owner.login,
repo: context.payload.repository.name,
issue_number: context.payload.number
});
core.info("labels: " + JSON.stringify(labels.data))
if ( labels.data.map(l => l.name).includes("no_release") ) {
core.info("Label found")
if ( labels.data.map(l => l.name).includes("pending_release") ) {
// Remove the 'pending_release' label
await github.rest.issues.removeLabel({
owner: context.payload.repository.owner.login,
repo: context.payload.repository.name,
issue_number: context.payload.pull_request.number,
name: 'pending_release'
})
}

return true
}
return false
- name: Display 'no_release' status
run: |
echo "no_release: ${{ steps.check_for_norelease_label.outputs.result }}"

check_ready_to_release:
runs-on: ubuntu-latest
needs: [check_for_norelease_label,lookup_default_branch]
if: |
needs.check_for_norelease_label.outputs.no_release == 'false'
outputs:
no_open_prs: ${{ steps.watch_dependabot_prs.outputs.is_complete }}
pending_release_pr_list: ${{ steps.get_release_pending_pr_list.outputs.result }}
ready_to_release: ${{ steps.set_ready_for_release.outputs.result }}
steps:
- name: Get Open PRs
id: get_open_pr_list
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
retries: 6 # final retry should wait 64 seconds
retry-exempt-status-codes: 400,401,404,422 # GH will raise rate limits with 403 & 429 status codes
# find all open PRs that are targetting the default branch (i.e. main/master)
# return their titles, so they can parsed later to determine if they are
# Dependabot PRs and whether we should wait for them to be auto-merged before
# allowing a release event.
script: |
const pulls = await github.rest.pulls.list({
owner: context.payload.repository.owner.login,
repo: context.payload.repository.name,
state: 'open',
base: '${{ needs.lookup_default_branch.outputs.branch_name }}'
});
return JSON.stringify(pulls.data.map(p=>p.title))
result-encoding: string
- name: Display open_pr_list
run: |
cat <<EOF
open_pr_list : ${{ steps.get_open_pr_list.outputs.result }}
EOF

- name: Get 'pending_release' PRs
id: get_release_pending_pr_list
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
retries: 6 # final retry should wait 64 seconds
retry-exempt-status-codes: 400,401,404,422 # GH will raise rate limits with 403 & 429 status codes
script: |
const repoWithOwner = `${context.payload.repository.owner.login}/${context.payload.repository.name}`;
const pulls = await github.rest.search.issuesAndPullRequests({
q: `is:pr repo:${repoWithOwner} is:merged`,
});
allPrs = pulls.data.items.map(p=>`#${p.number} '${p.title}' in ${p.repository_url}`);
core.info(`allPrs: ${JSON.stringify(allPrs)}`);

releasePendingPrDetails = pulls.data.items.
filter(function (x) { return x.labels.map(l=>l.name).includes('pending_release') }).
filter(function (x) { return !x.labels.map(l=>l.name).includes('no_release') }).
map(p=>`#${p.number} '${p.title}' in ${p.repository_url}`);
core.info(`releasePendingPrDetails: ${JSON.stringify(releasePendingPrDetails)}`);

const release_pending_prs = pulls.data.items.
filter(function (x) { return x.labels.map(l=>l.name).includes('pending_release') }).
filter(function (x) { return !x.labels.map(l=>l.name).includes('no_release') }).
map(p=>p.number);
core.info(`release_pending_prs: ${JSON.stringify(release_pending_prs)}`);
core.setOutput('is_release_pending', (release_pending_prs.length > 0));
return JSON.stringify(release_pending_prs);
result-encoding: string

- name: Display release_pending_pr_list
run: |
cat <<EOF
release_pending_pr_list : ${{ steps.get_release_pending_pr_list.outputs.result }}
EOF
echo "is_release_pending : ${{ steps.get_release_pending_pr_list.outputs.is_release_pending }}"

- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
- name: Read pr-autoflow configuration
id: get_pr_autoflow_config
uses: endjin/pr-autoflow/actions/read-configuration@v4
with:
config_file: .github/config/pr-autoflow.json

- name: Check Human PR
id: is_human_pr
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
retries: 6 # final retry should wait 64 seconds
retry-exempt-status-codes: 400,401,404,422 # GH will raise rate limits with 403 & 429 status codes
script: |
return context.payload.pull_request.user.login != 'dependabot[bot]' && context.payload.pull_request.user.login != 'dependjinbot[bot]'

- name: Watch Dependabot PRs
id: watch_dependabot_prs
uses: endjin/pr-autoflow/actions/dependabot-pr-watcher@v4
with:
pr_titles: ${{ steps.get_open_pr_list.outputs.result }}
package_wildcard_expressions: ${{ steps.get_pr_autoflow_config.outputs.AUTO_MERGE_PACKAGE_WILDCARD_EXPRESSIONS }}
max_semver_increment: minor
verbose_mode: 'False'

- name: Set Ready for Release
id: set_ready_for_release
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
retries: 6 # final retry should wait 64 seconds
retry-exempt-status-codes: 400,401,404,422 # GH will raise rate limits with 403 & 429 status codes
script: |
return ( '${{ steps.is_human_pr.outputs.result }}' == 'true' || '${{ steps.watch_dependabot_prs.outputs.is_complete }}' == 'True') && '${{ steps.get_release_pending_pr_list.outputs.is_release_pending }}' == 'true'

- name: Display job outputs
run: |
echo "no_open_prs: ${{ steps.watch_dependabot_prs.outputs.is_complete }}"
cat <<EOF
pending_release_pr_list: ${{ steps.get_release_pending_pr_list.outputs.result }}
EOF
echo "is_human_pr: ${{ steps.is_human_pr.outputs.result }}"
echo "ready_to_release : ${{ steps.set_ready_for_release.outputs.result }}"

tag_for_release:
runs-on: ubuntu-latest
needs: [check_ready_to_release,lookup_default_branch]
if: |
needs.check_ready_to_release.outputs.ready_to_release == 'true'
steps:
- uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
with:
dotnet-version: '6.x'

- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
with:
# ensure we are creating the release tag on the default branch
ref: ${{ needs.lookup_default_branch.outputs.branch_name }}
fetch-depth: 0

- name: Install GitVersion
run: |
dotnet tool install -g GitVersion.Tool --version 5.8.0
echo "/github/home/.dotnet/tools" >> $GITHUB_PATH
- name: Run GitVersion
id: run_gitversion
run: |
pwsh -noprofile -c 'dotnet-gitversion /diag'

- name: Generate token
id: generate_token
uses: tibdex/github-app-token@32691ba7c9e7063bd457bd8f2a5703138591fa58 # v1.9
with:
app_id: ${{ secrets.ENDJIN_BOT_APP_ID }}
private_key: ${{ secrets.ENDJIN_BOT_PRIVATE_KEY }}

- name: Create SemVer tag
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
github-token: ${{ steps.generate_token.outputs.token }}
retries: 6 # final retry should wait 64 seconds
retry-exempt-status-codes: 400,401,404,422 # GH will raise rate limits with 403 & 429 status codes
script: |
const uri_path = '/repos/' + context.payload.repository.owner.login + '/' + context.payload.repository.name + '/git/refs'
const tag = await github.request(('POST ' + uri_path), {
owner: context.payload.repository.owner.login,
repo: context.payload.repository.name,
ref: 'refs/tags/${{ env.GitVersion_MajorMinorPatch }}',
sha: '${{ needs.lookup_default_branch.outputs.head_commit }}'
})

- name: Remove 'release_pending' label from PRs
id: remove_pending_release_labels
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
github-token: '${{ steps.generate_token.outputs.token }}'
retries: 6 # final retry should wait 64 seconds
retry-exempt-status-codes: 400,401,404,422 # GH will raise rate limits with 403 & 429 status codes
script: |
core.info('PRs to unlabel: ${{ needs.check_ready_to_release.outputs.pending_release_pr_list }}')
const pr_list = JSON.parse('${{ needs.check_ready_to_release.outputs.pending_release_pr_list }}')
core.info(`pr_list: ${pr_list}`)
for (const i of pr_list) {
core.info(`Removing label 'pending_release' from issue #${i}`)
github.rest.issues.removeLabel({
owner: context.payload.repository.owner.login,
repo: context.payload.repository.name,
issue_number: i,
name: 'pending_release'
});
}
83 changes: 83 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: build
on:
push:
branches:
- main
tags:
- '*'
pull_request:
branches:
- main
workflow_dispatch:
inputs:
forcePublish:
description: When true the Publish stage will always be run, otherwise it only runs for tagged versions.
required: false
default: false
type: boolean
forcePublicNugetDestination:
description: When true the NuGet Publish destination will always be set to nuget.org. (Normally, force publishing publishes to the private feed on GitHub.)
required: false
default: false
type: boolean
skipCleanup:
description: When true the pipeline clean-up stage will not be run. For example, the cache used between pipeline stages will be retained.
required: false
default: false
type: boolean

concurrency:
group: ${{ github.workflow }}-${{ github.sha }}
cancel-in-progress: true

permissions:
actions: write # enable cache clean-up
checks: write # enable test result annotations
contents: write # enable creating releases
issues: read
packages: write # enable publishing packages
pull-requests: write # enable test result annotations

jobs:
prepareConfig:
name: Prepare Configuration
runs-on: ubuntu-latest
outputs:
RESOLVED_ENV_VARS: ${{ steps.prepareEnvVarsAndSecrets.outputs.environmentVariablesYamlBase64 }}
RESOLVED_SECRETS: ${{ steps.prepareEnvVarsAndSecrets.outputs.secretsYamlBase64 }}
steps:
# Declare any environment variables and/or secrets that need to be available inside the build process
- uses: endjin/Endjin.RecommendedPractices.GitHubActions/actions/prepare-env-vars-and-secrets@main
id: prepareEnvVarsAndSecrets
with:
environmentVariablesYaml: |
ZF_NUGET_PUBLISH_SOURCE: ${{ (startsWith(github.ref, 'refs/tags/') || github.event.inputs.forcePublicNugetDestination == 'true') && 'https://api.nuget.org/v3/index.json' || format('https://nuget.pkg.github.com/{0}/index.json', github.repository_owner) }}
secretsYaml: |
NUGET_API_KEY: "${{ startsWith(github.ref, 'refs/tags/') && secrets.ENDJIN_NUGET_APIKEY || secrets.ENDJIN_GITHUB_PUBLISHER_PAT }}"
secretsEncryptionKey: ${{ secrets.SHARED_WORKFLOW_KEY }}

build:
needs: prepareConfig
uses: endjin/Endjin.RecommendedPractices.GitHubActions/.github/workflows/scripted-build-pipeline.yml@main
with:
netSdkVersion: '8.x'
additionalNetSdkVersion: '9.x'
# workflow_dispatch inputs are always strings, the type property is just for the UI
forcePublish: ${{ github.event.inputs.forcePublish == 'true' }}
skipCleanup: ${{ github.event.inputs.skipCleanup == 'true' }}
# testArtifactName: ''
# testArtifactPath: ''
compilePhaseEnv: ${{ needs.prepareConfig.outputs.RESOLVED_ENV_VARS }}
testPhaseEnv: ${{ needs.prepareConfig.outputs.RESOLVED_ENV_VARS }}
packagePhaseEnv: ${{ needs.prepareConfig.outputs.RESOLVED_ENV_VARS }}
publishPhaseEnv: ${{ needs.prepareConfig.outputs.RESOLVED_ENV_VARS }}
secrets:
compilePhaseAzureCredentials: ${{ secrets.ENDJIN_PROD_ACR_READER_CREDENTIALS }}
# testPhaseAzureCredentials: ${{ secrets.ENDJIN_TEST_KV_READER_CREDENTIALS }}
# packagePhaseAzureCredentials: ${{ secrets.AZURE_PUBLISH_CREDENTIALS }}
# publishPhaseAzureCredentials: ${{ secrets.AZURE_PUBLISH_CREDENTIALS }}
# compilePhaseSecrets: ${{ needs.prepareConfig.outputs.RESOLVED_SECRETS }}
# testPhaseSecrets: ${{ needs.prepareConfig.outputs.RESOLVED_SECRETS }}
# packagePhaseSecrets: ${{ needs.prepareConfig.outputs.RESOLVED_SECRETS }}
publishPhaseSecrets: ${{ needs.prepareConfig.outputs.RESOLVED_SECRETS }}
secretsEncryptionKey: ${{ secrets.SHARED_WORKFLOW_KEY }}
Loading
Loading