From 449c85eff6eb3b286c658c48f6dc92123e8ec70c Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 30 Aug 2025 14:29:43 +0200 Subject: [PATCH 1/7] deploy: allow deploying the Function manually When forking GitGitGadget's GitHub App, GitHub Actions are turned off by default. Allow deploying the App in such circumstances by adding a workflow dispatch trigger. Signed-off-by: Johannes Schindelin --- .github/workflows/deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 27d55e7..ad037b3 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,6 +1,7 @@ name: Deploy to Azure on: + workflow_dispatch: push: branches: - main From 0299d1bf6079e9b76faa6ed83a41b7bf525e19eb Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 21 Apr 2024 23:00:52 +0200 Subject: [PATCH 2/7] deploy: allow overriding the Azure Function name via a repo variable This allows overriding the default Azure Function name used for deploying via setting a repository secret (in Settings>Secrets and variables). The name of the Azure Function is treated as a secret because it can be used to derive the URL of the Azure Function and to attempt to DOS it. At this point, it is not _actually_ necessary to provide the correct Azure Function app name here, as we are using a so-called "publish profile" that overrides the app name. However, we are about to switch to Role-Based Access Control, where it very much matters whether the correct name is specified or not. Signed-off-by: Johannes Schindelin --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ad037b3..d09169c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -18,6 +18,6 @@ jobs: - uses: actions/checkout@v5 - uses: Azure/functions-action@v1 with: - app-name: GitGitGadget + app-name: ${{ secrets.AZURE_FUNCTION_NAME || 'GitGitGadget' }} publish-profile: ${{ secrets.AZURE_FUNCTIONAPP_PUBLISH_PROFILE }} respect-funcignore: true From 58b1bb967cd687df1b1b561b1b074f9b52a66726 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 21 Apr 2024 00:40:45 +0200 Subject: [PATCH 3/7] deploy: use OpenID Connect instead of a publish profile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apparently the `publish-profile` deployments are no longer working as expected for recently-created Azure Functions. That is, the existing `gitgitgadget` Function still works, obviously, but when I registered a new Function as described in the `README.md` and tried to deploy it the same way as `gitgitgadget`, it failed thusly: ▶ Run Azure/functions-action@v1 Successfully parsed SCM credential from publish-profile format. Using SCM credential for authentication, GitHub Action will not perform resource validation. (node:1549) [DEP0005] DeprecationWarning: Buffer() is deprecated due to security and usability issues. Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead. (Use `node --trace-deprecation ...` to show where the warning was created) Error: Execution Exception (state: ValidateAzureResource) (step: Invocation) Error: When request Azure resource at ValidateAzureResource, Get Function App Settings : Failed to acquire app settings from https:///api/settings with publish-profile Error: Failed to fetch Kudu App Settings. Unauthorized (CODE: 401) Error: Error: Failed to fetch Kudu App Settings. Unauthorized (CODE: 401) at Kudu. (/home/runner/work/_actions/Azure/functions-action/v1/lib/appservice-rest/Kudu/azure-app-kudu-service.js:69:23) at Generator.next () at fulfilled (/home/runner/work/_actions/Azure/functions-action/v1/lib/appservice-rest/Kudu/azure-app-kudu-service.js:5:58) at processTicksAndRejections (node:internal/process/task_queues:96:5) Error: Deployment Failed! My guess is that finally the reality of publish profiles being highly insecure has caught up with new Azure Function registrations, and it is now required to use much more secure methods instead. Let's use OpenID Connect, as it is tied to the GitHub workflow and is therefore as secure as it gets. Even if the name of the Managed Identity, the tenant and the subscription IDs are known, an attacker cannot authenticate as that managed identity. Signed-off-by: Johannes Schindelin --- .github/workflows/deploy.yml | 11 ++++++++++- README.md | 26 +++++++++++++++++++++----- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d09169c..73963fe 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -9,6 +9,10 @@ on: - '.github/workflows/deploy.yml' - 'GitGitGadget/**' +permissions: + contents: read + id-token: write + jobs: deploy: if: github.event.repository.fork == false @@ -16,8 +20,13 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 + - name: 'Login via Azure CLI' + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - uses: Azure/functions-action@v1 with: app-name: ${{ secrets.AZURE_FUNCTION_NAME || 'GitGitGadget' }} - publish-profile: ${{ secrets.AZURE_FUNCTIONAPP_PUBLISH_PROFILE }} respect-funcignore: true diff --git a/README.md b/README.md index b9317f0..34b336c 100644 --- a/README.md +++ b/README.md @@ -53,9 +53,27 @@ This process looks a bit complex, the main reason for that being that three thin First of all, a new [Azure Function](https://portal.azure.com/#blade/HubsExtension/BrowseResourceBlade/resourceType/Microsoft.Web%2Fsites/kind/functionapp) was created. A Linux one was preferred, for cost and performance reasons. Deployment with GitHub was _not_ yet configured. -#### Getting the "publish profile" +#### Obtaining the Azure credentials + +The idea is to use [OpenID Connect](https://docs.github.com/en/actions/concepts/security/openid-connect) to log into Azure in the deploy workflow, _identifying_ as said workflow, via a "Managed Identity". This can be registered after the Azure Function has been successfully created: In an Azure CLI (for example [the one that is very neatly embedded in the Azure Portal](https://learn.microsoft.com/en-us/azure/cloud-shell/get-started/classic)), run this (after replacing the placeholders `{subscription-id}`, `{resource-group}` and `{app-name}`): + +```shell +az identity create --name -g +az identity federated-credential create \ + --identity-name \ + --resource-group \ + --name github-workflow \ + --issuer https://token.actions.githubusercontent.com \ + --subject repo:/gitgitgadget-github-app:environment:deploy-to-azure \ + --audiences api://AzureADTokenExchange +# The scope can be copied from the Azure Portal URL after navigating to the Azure Function +az role assignment create \ + --assignee \ + --scope '/subscriptions//resourceGroups//providers/Microsoft.Web/sites/' \ + --role 'Contributor' +``` -After the deployment succeeded, in the "Overview" tab, there is a "Get publish profile" link on the right panel at the center top. Clicking it will automatically download a `.json` file whose contents will be needed later. +The result is a "managed identity", essentially a tightly-scoped credential that allows deploying this particular Azure Function from that particular repository in a GitHub workflow run and that's it. This managed identity is identified via the `AZURE_CLIENT_ID`, `AZURE_TENANT_ID` and `AZURE_SUBSCRIPTION_ID` Actions secrets, more on that below. #### Some environment variables @@ -67,9 +85,7 @@ Also, the `GITHUB_APP_ID` and `GITHUB_APP_PRIVATE_KEY` variables are needed in o ### The repository -On https://github.com/, the `+` link on the top was pressed, and an empty, private repository was registered. Nothing was pushed to it yet. - -After that, the contents of the publish profile that [was downloaded earlier](#getting-the-publish-profile) were registered as Actions secret, under the name `AZURE_FUNCTIONAPP_PUBLISH_PROFILE`. +Create a fork of https://github.com/gitgitgadget/gitgitgadget-github-app. Configure the Azure Managed Identity via Actions secrets, under the keys `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, and `AZURE_SUBSCRIPTION_ID`. Also, the `AZURE_FUNCTION_NAME` secret needs to be defined (its value is the name of the Azure Function). This repository was initialized locally by forking https://github.com/gitgitgadget/gitgitgadget and separating out the Azure Functions part of it. Then, the test suite was developed and the GitHub workflows were adapted from https://github.com/git-for-windows/gfw-helper-github-app. After that, the `origin` remote was set to the newly registered repository on GitHub. From a1a0dde38f3986cfc7799c5e361def44e6c4bdd7 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 30 Aug 2025 20:57:13 +0200 Subject: [PATCH 4/7] Configure the project via `gitgitgadget-config.json` With this change, the GitGitGadget GitHub App truly learns about projects other than Git. Instead of hard-coding the respective project-dependent values, it now reads the project configuration from the `gitgitgadget-config.json` file, which adheres to the `IConfig` interface defined in https://github.com/gitgitgadget/gitgitgadget/blob/HEAD/lib/project-config.ts. One caveat: Since the App needs to know which `gitgitgadget-workflows` fork to target when triggering the GitHub workflows, an additional entry is required in the configuration that is _not_ defined in `IConfig`: workflowsRepo: { owner: "gitgitgadget-workflows", name: "gitgitgadget-workflows" } Signed-off-by: Johannes Schindelin --- GitGitGadget/gitgitgadget-config.json | 78 +++++++++++++++++++++++++++ GitGitGadget/index.js | 24 ++++----- __tests__/index.test.js | 2 +- 3 files changed, 89 insertions(+), 15 deletions(-) create mode 100644 GitGitGadget/gitgitgadget-config.json diff --git a/GitGitGadget/gitgitgadget-config.json b/GitGitGadget/gitgitgadget-config.json new file mode 100644 index 0000000..3104f87 --- /dev/null +++ b/GitGitGadget/gitgitgadget-config.json @@ -0,0 +1,78 @@ +{ + "repo": { + "name": "git", + "owner": "gitgitgadget", + "baseOwner": "git", + "testOwner": "dscho", + "owners": [ + "gitgitgadget", + "git", + "dscho" + ], + "branches": [ + "maint", + "seen" + ], + "closingBranches": [ + "maint", + "master" + ], + "trackingBranches": [ + "maint", + "seen", + "master", + "next" + ], + "maintainerBranch": "gitster", + "host": "github.com" + }, + "mailrepo": { + "name": "git", + "owner": "gitgitgadget", + "branch": "master", + "host": "lore.kernel.org", + "url": "https://lore.kernel.org/git/", + "public_inbox_epoch": 1, + "mirrorURL": "https://github.com/gitgitgadget/git-mailing-list-mirror", + "mirrorRef": "refs/heads/lore-1", + "descriptiveName": "lore.kernel/git" + }, + "mail": { + "author": "GitGitGadget", + "sender": "GitGitGadget", + "smtpUser": "gitgitgadget@gmail.com", + "smtpHost": "smtp.gmail.com" + }, + "app": { + "appID": 12836, + "installationID": 195971, + "name": "gitgitgadget", + "displayName": "GitGitGadget", + "altname": "gitgitgadget-git" + }, + "lint": { + "maxCommitsIgnore": [ + "https://github.com/gitgitgadget/git/pull/923" + ], + "maxCommits": 30 + }, + "user": { + "allowUserAsLogin": false + }, + "syncUpstreamBranches": [ + { + "sourceRepo": "gitster/git", + "targetRepo": "gitgitgadget/git", + "sourceRefRegex": "^refs/heads/(maint-\\d|[a-z][a-z]/)" + }, + { + "sourceRepo": "j6t/git-gui", + "targetRepo": "gitgitgadget/git", + "targetRefNamespace": "git-gui/" + } + ], + "workflowsRepo": { + "owner": "gitgitgadget-workflows", + "name": "gitgitgadget-workflows" + } +} diff --git a/GitGitGadget/index.js b/GitGitGadget/index.js index c26145e..cddc453 100644 --- a/GitGitGadget/index.js +++ b/GitGitGadget/index.js @@ -26,14 +26,10 @@ module.exports = async (context, req) => { } try { - /* - * For various reasons, the GitGitGadget GitHub App can be installed - * on any random repository. However, GitGitGadget only wants to support - * the `gitgitgadget/git` and the `git/git` repository (with the - * `dscho/git` one thrown in for debugging purposes). - */ - const orgs = ['gitgitgadget', 'git', 'dscho'] - const a = [context, undefined, 'gitgitgadget-workflows', 'gitgitgadget-workflows'] + const { readFileSync } = require('fs') + const config = JSON.parse(readFileSync(`${__dirname}/gitgitgadget-config.json`)) + const orgs = config.repo.owners + const a = [context, undefined, config.workflowsRepo.owner, config.workflowsRepo.name] const eventType = context.req.headers['x-github-event']; context.log(`Got eventType: ${eventType}`); @@ -41,7 +37,7 @@ module.exports = async (context, req) => { if (!orgs.includes(repositoryOwner)) { context.res = { status: 403, - body: 'Refusing to work on a repository other than gitgitgadget/git or git/git' + body: `Refusing to work on any repository outside of ${orgs.join(', ')}`, }; } else if (eventType === 'pull_request') { if (req.body.action !== 'opened' && req.body.action !== 'synchronize') { @@ -59,9 +55,9 @@ module.exports = async (context, req) => { body: `Ignored event type: ${eventType}`, }; } else if (eventType === 'push') { - if (req.body.repository.full_name ==='gitgitgadget/git-mailing-list-mirror') { + if (config.mailrepo.mirrorURL === `https://github.com/${req.body.repository.full_name}`) { context.res = { body: `push(${req.body.ref} in ${req.body.repository.full_name}): ` } - if (req.body.ref === 'refs/heads/lore-1') { + if (req.body.ref === config.mailrepo.mirrorRef) { const queued = await listWorkflowRuns(...a, 'handle-new-mails.yml', 'queued') if (queued.length) { context.res.body += [ @@ -73,7 +69,7 @@ module.exports = async (context, req) => { context.res.body += `triggered ${run.html_url}` } } else context.res.body += `Ignoring non-default branches` - } else if (req.body.repository.full_name !== 'git/git') { + } else if (req.body.repository.full_name !== `${config.repo.baseOwner}/${config.repo.name}`) { context.res = { body: `Ignoring pushes to ${req.body.repository.full_name}` } } else { const run = await triggerWorkflowDispatch( @@ -84,7 +80,7 @@ module.exports = async (context, req) => { } ) const extra = [] - if (req.body.ref === 'refs/heads/seen') { + if (config.repo.branches.map((name) => `refs/heads/${name}`).includes(req.body.ref)) { for (const workflow of ['update-prs.yml', 'update-mail-to-commit-notes.yml']) { if ((await listWorkflowRuns(...a, workflow, 'main', 'queued')).length === 0) { const run = await triggerWorkflowDispatch(...a, workflow, 'main') @@ -103,7 +99,7 @@ module.exports = async (context, req) => { } /* GitGitGadget works on dscho/git only for testing */ - if (repositoryOwner === 'dscho' && comment.user.login !== 'dscho') { + if (repositoryOwner === config.repo.testOwner && comment.user.login !== config.repo.testOwner) { throw new Error(`Ignoring comment from ${comment.user.login}`); } diff --git a/__tests__/index.test.js b/__tests__/index.test.js index 7db164e..eca133a 100644 --- a/__tests__/index.test.js +++ b/__tests__/index.test.js @@ -162,7 +162,7 @@ testIssueComment('/test', async (context) => { testIssueComment('/verify-repository', 'nope', (context) => { expect(context.done).toHaveBeenCalledTimes(1) expect(context.res).toEqual({ - body: 'Refusing to work on a repository other than gitgitgadget/git or git/git', + body: 'Refusing to work on any repository outside of gitgitgadget, git, dscho', 'status': 403, }) expect(mockRequest.write).not.toHaveBeenCalled() From bc379094badddf239b85cd782eca2f60f8a6867d Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 30 Aug 2025 16:58:34 +0200 Subject: [PATCH 5/7] deploy: allow deploying in forks It would be nice if we could deploy the Azure Function contingent on the presence of the `AZURE_CLIENT_ID` secret. However, this is not possible in GitHub workflows: the job-level `if:` conditions lack access to the `secrets` context. Strangely enough, they _do_ have access to the `vars` context... To successfully deploy the Azure Function, it needs to know which `gitgitgadget-workflows` fork to target when triggering workflow runs, anyway, so let's _require_ a repository variable called `DEPLOY_WITH_WORKFLOWS` that specifies that fork in the form `/gitgitgadget-workflows`. Note that such a `gitgitgadget-workflows` fork _must_ have the `config` branch, with a `gitgitgadget-config.json` file that contains the corresponding project configuration; The `deploy` workflow will retrieve this configuration and overwrite `gitgitgadget-config.json` with it, augmenting the `workflowsRepo` information on the fly. Signed-off-by: Johannes Schindelin --- .github/workflows/deploy.yml | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 73963fe..c660b0f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -15,11 +15,39 @@ permissions: jobs: deploy: - if: github.event.repository.fork == false + if: github.event.repository.fork == false || vars.DEPLOY_WITH_WORKFLOWS != '' environment: deploy-to-azure runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 + - name: parse DEPLOY_WITH_WORKFLOWS + if: vars.DEPLOY_WITH_WORKFLOWS != '' && contains(vars.DEPLOY_WITH_WORKFLOWS, '/') + id: parsed + env: + WORKFLOWS_REPO: '${{ vars.DEPLOY_WITH_WORKFLOWS }}' + run: | + echo "owner=${WORKFLOWS_REPO%%/*}" >>$GITHUB_OUTPUT && + echo "name=${WORKFLOWS_REPO#*/}" >>$GITHUB_OUTPUT + - name: retrieve `vars.CONFIG` from workflows repo + if: vars.DEPLOY_WITH_WORKFLOWS != '' + env: + WORKFLOWS_REPO: '${{ vars.DEPLOY_WITH_WORKFLOWS }}' + GH_TOKEN: ${{ steps.workflows-repo-token.outputs.token || secrets.GITHUB_TOKEN }} + run: | + set -x && + if ! curl -fLO https://github.com/"$WORKFLOWS_REPO"/raw/config/gitgitgadget-config.json + then + echo "::error::Could not retrieve 'gitgitgadget-config.json' from the 'config' branch of $WORKFLOWS_REPO" + exit 1 + fi && + jq '. + { + "workflowsRepo": { + "owner": "${{ steps.parsed.outputs.owner }}", + "name": "${{ steps.parsed.outputs.name }}" + } + }' GitGitGadget/gitgitgadget-config.json && + echo "Using the following configuration:" && + cat GitGitGadget/gitgitgadget-config.json - name: 'Login via Azure CLI' uses: azure/login@v2 with: From 5ca2ad1d357b7a525ac51c2e9fa4faaf5a773e46 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 30 Aug 2025 17:55:26 +0200 Subject: [PATCH 6/7] funcignore: skip more files when deploying Signed-off-by: Johannes Schindelin --- .funcignore | 3 +++ .github/workflows/deploy.yml | 1 + 2 files changed, 4 insertions(+) diff --git a/.funcignore b/.funcignore index 8d5be39..0decfbc 100644 --- a/.funcignore +++ b/.funcignore @@ -2,3 +2,6 @@ /*.md /host.json /local.settings.json +/package.json +/package-lock.json +/jest.config.js diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c660b0f..d3c728f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -6,6 +6,7 @@ on: branches: - main paths: + - '.funcignore' - '.github/workflows/deploy.yml' - 'GitGitGadget/**' From a4b930ca30f58f7de41decf90654d7be6d3fc03a Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 30 Aug 2025 18:15:15 +0200 Subject: [PATCH 7/7] README: elaborate on registering the GitHub App Now that I am trying to establish support for projects other than Git, I need to register a new GitHub App. Let's document that process better for the next person who wants to repeat that feat. Signed-off-by: Johannes Schindelin --- README.md | 62 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 34b336c..e4f54a8 100644 --- a/README.md +++ b/README.md @@ -45,13 +45,13 @@ Finally, [run the Function locally](https://learn.microsoft.com/en-us/azure/azur You can also run/debug it via VS Code, there is a default configuration called "Attach to Node Functions". -## How this GitHub App was set up +## How to set up this GitHub App This process looks a bit complex, the main reason for that being that three things have to be set up essentially simultaneously: an Azure Function, a GitHub repository and a GitHub App. ### The Azure Function -First of all, a new [Azure Function](https://portal.azure.com/#blade/HubsExtension/BrowseResourceBlade/resourceType/Microsoft.Web%2Fsites/kind/functionapp) was created. A Linux one was preferred, for cost and performance reasons. Deployment with GitHub was _not_ yet configured. +First of all, a new [Azure Function](https://portal.azure.com/#blade/HubsExtension/BrowseResourceBlade/resourceType/Microsoft.Web%2Fsites/kind/functionapp) needs to be created. A Linux one is preferred, with a regular Consumption plan, for [cost](https://azure.microsoft.com/en-us/pricing/details/functions/) and performance reasons. Deployment with GitHub should _not_ yet be configured. #### Obtaining the Azure credentials @@ -77,26 +77,68 @@ The result is a "managed identity", essentially a tightly-scoped credential that #### Some environment variables -A few environment variables will have to be configured for use with the Azure Function. This can be done on the "Configuration" tab, which is in the "Settings" group. +A few environment variables need to be configured for use with the Azure Function. This can be done on the "Configuration" tab, which is in the "Settings" group. -Concretely, the environment variables `GITHUB_WEBHOOK_SECRET` and `GITGITGADGET_TRIGGER_TOKEN` (a Personal Access Token to trigger the Azure Pipelines) need to be set. For the first, a generated random string was used. The second one was [created](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows#create-a-pat) scoped to the Azure DevOps project `gitgitgadget` with the Build (read & execute) permissions. +Concretely, the environment variables `GITHUB_WEBHOOK_SECRET` needs to be set, any generated random string can be used as its value (and the same value needs to be used when eventually registering the actual GitHub App). -Also, the `GITHUB_APP_ID` and `GITHUB_APP_PRIVATE_KEY` variables are needed in order to trigger GitHub workflow runs. These were obtained as part of registering the GitHub App. +Also, the `GITHUB_APP_ID` and `GITHUB_APP_PRIVATE_KEY` variables are needed in order to trigger GitHub workflow runs. These are obtained as part of registering the GitHub App (see below). ### The repository Create a fork of https://github.com/gitgitgadget/gitgitgadget-github-app. Configure the Azure Managed Identity via Actions secrets, under the keys `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, and `AZURE_SUBSCRIPTION_ID`. Also, the `AZURE_FUNCTION_NAME` secret needs to be defined (its value is the name of the Azure Function). -This repository was initialized locally by forking https://github.com/gitgitgadget/gitgitgadget and separating out the Azure Functions part of it. Then, the test suite was developed and the GitHub workflows were adapted from https://github.com/git-for-windows/gfw-helper-github-app. After that, the `origin` remote was set to the newly registered repository on GitHub. +Also configure the repository _variable_ `DEPLOY_WITH_WORKFLOWS`; Its value must correspond to the fork of https://github.com/gitgitgadget/gitgitgadget-workflows, in the form `/gitgitgadget-workflows`. Note that that fork _must_ have a `config` branch that contains a valid project configuration in its `gitgitgadget-config.json` file. -As a last step, the repository was pushed, triggering the deployment to the Azure Function. +As a last step, on the Actions tab, the `Deploy to Azure` workflow needs to be triggered manually, which deploys the Azure Function. ### The GitHub App -Finally, the existing GitHub App's webhook URL was redirected to the new one. If there had not been an existing GitHub App, [a new GitHub App would have been registered](https://github.com/settings/apps/new) with https://github.com/gitgitgadget as homepage URL. +Now it is finally time to [register a new GitHub App](https://github.com/settings/apps/new) with https://github.com/> as homepage URL. -As Webhook URL, the URL of the Azure Function was used, which can be copied in the "Functions" tab of the Azure Function. It looks similar to this: https://my-github-app.azurewebsites.net/api/MyGitHubApp +As Webhook URL, use the URL of the Azure Function, which should look like this: https://.azurewebsites.net/api/GitGitGadget The value stored in the Azure Function as `GITHUB_WEBHOOK_SECRET` was used as Webhook secret. -The GitGitGadget GitHub app requires the following permissions: Read access to metadata, and Read and write access to checks, code, commit statuses, issues, pull requests, and workflows. \ No newline at end of file +The GitGitGadget GitHub app requires the following permissions: Read access to metadata, Read and write access to Variables, Actions, Checks, Commit statuses, Contents, Issues, Pull requests, and Workflows. It needs the following webhook events to be enabled: Check run, Commit comment, Issue comment, Pull request, Pull request review, Pull request review comment, Push, Repository, and Status. + +Once the GitHub App is successfully registered (and unfortunately only then), the private key can be generated via clicking the `Generate a private key` button in the "Private keys" section toward the bottom. This will automatically download a file; The contents of that file, with newlines replaced by `\n`, need to be configured as `GITHUB_APP_PRIVATE_KEY` environment variable in your Azure Function's `Settings>Environment variables` tab, and `GITHUB_APP_ID` needs to be set, too (it can be seen on the GitHub App's page at the top, labeled as "App ID"). + +The app needs to be installed on the fork of the `gitgitgadget-workflows` repository, and the app ID and private key should also be stored as Actions secrets in the fork of the `gitgitgadget-github-app` repository and it should be re-deployed so that it can pick up those new bits and pieces. + +#### Using `register-github-app-cli` + +A convenient alternative to clicky-clicky in the GitHub UI to register the GitHub is the convenient [`npx register-github-app-cli` command](https://github.com/gr2m/register-github-app-cli): Use it with `--org ` and a variation of this manifest: + +```yml +name: +url: https://github.com/apps/ +hook_attributes: + url: https://.azurewebsites.net/api/GitGitGadget +public: false +default_permissions: + actions: write + checks: write + commit_statuses: write + contents: write + issues: write + metadata: read + pull_requests: write + variables: read + workflows: write +default_events: + - check_run + - commit_comment + - issue_comment + - pull_request + - pull_request_review + - pull_request_review_comment + - push + - repository + - status +``` + +### A read-only GitHub App + +In complex setups, like the one for the Git project, there is more than one repository in which users open Pull Requests that then get forwarded by GitGitGadget: In addition to the "pr-repo", there can be an "upstream-repo" that is owned by the upstream project and should not allow GitGitGadget to write to it. + +To this end, a second GitHub App can be registered, one that lacks all permissions except Read/write on `issues` & `pull_requests` (to write PR comments) and `checks` (to mirror the workflow runs to the PRs). This will need to be configured on the `gitgitgadget-workflows` fork as `GITGITGADGET_READONLY_GITHUB_APP_ID` and `GITGITGADGET_READONLY_GITHUB_APP_PRIVATE_KEY` secrets.