Triggering from a deployment for a forked pull request merge commit

Hi Everyone,

I would like to deploy my code using GitHub Actions and Deployments.

Sometimes I want to deploy (to dev of course) from a pull request (so I can test it before merging).

I create a deployment with the ref being ref/pull/[PULL_REQUEST_ID]/merge.

In that PR I have a workflow that is triggered on “deployment”.

I would expect that to run, however GitHub Actions is having an error since the pull request is from a forked repo.

The error message reads:
This run was triggered by the commit `SOME_SHA` which is not referenced by any branches or tags in this repository. It is either an orphaned commit or from a fork of this repository.

Any ideas how I can trigger an action from a deployment for a ref that is from a forked pull request?

Thanks!

GitHub will tell you that doing this is incredibly dangerous. And they’re right.

If you’re going to do something like this, I’d encourage you to create a fork (or clone) of your repository and instead of running deployments in your main repository, trigger deployments in your fork. (The fork doesn’t even need to be in the fork network for your main repository.)

If I was doing that, I’d create an ssh deploy key for my fork, put the private key into my main repository’s secrets and then I’d do something like:

git checkout -b deploy-$PR_NUMBER
echo "$MY_DEPLOY_KEY" > ~/.ssh/id_rsa
chmod 0400 ~/.ssh/id_rsa
git remote add fork git@github.com:fork-org/myrepo.git
git push fork deploy-$PR_NUMBER

And then sprinkle whatever magic is necessary for the fork to respond to the push to trigger the deploy.


The error message is technically correct. ref/pull/.../merge is not a branch or tag in your repository, you said so yourself.

By creating a branch deploy-$PR_NUMBER, and then pushing it into fork, the above snippet would establish a branch that is present in fork and thus satisfy the requirement. And by not putting it into a repository that people clone/fork/do things w/, you reduce the attack surface a smidgen (it’s still as dangerous as deploying untrusted code on a deployment can be, because it’s exactly that, but that’s beyond the scope here).


In response to pushes to your default branch in your main repository, you can push your default branch forward in your fork if you like, that would enable them to be a bit synchronized…

But be sure that your workflows explicitly check the org+repo name and only run if they’re operating on the right repository – see docs/open-enterprise-issue.yml at f5386da34890d88fa83c6b007316d76a0b862694 · github/docs · GitHub / docs/confirm-internal-staff-work-in-docs.yml at f5386da34890d88fa83c6b007316d76a0b862694 · github/docs · GitHub for example.

You don’t want this code to run on forks that other users make of your repository – it’s really frustrating when workflows run in forks when they’re really only designed to run in a specific repository.

Thanks @jsoref - That was a greatly detailed answer.

I totally hear what you are saying about the risk and security issues raised by this.

However, opening a PR is only for users with access to my private repo, so in my case I am protected… still I see how this can be an issue.

My problem is that the deploy requires several secrets (like AWS credentials) and I dont want to have to manage them for all of developers forks :-/

I think your approach is a good idea (assuming that GitHub will continue to not allow this) - Any idea how I can do the above BASH example but using the API? Essentially taking a branch of a fork and pushing it to the origin?

Thanks again for your detailed answer and time, really appreciate it!
Mo

Not sure what you mean by “API”

https://docs.github.com/en/rest/reference/git#create-a-reference

If you mean octokit, then, I guess the above covers it.

If your fork is connected, then:

await octokit.request('POST /repos/{owner}/{repo}/git/refs', {
  owner: 'fork-org',
  repo: 'myrepo',
  ref: 'deploy-PR-5',
  sha: 'aae898797811aaaefff'
})

should just work, where sha contains the value from the current merge commit and ref contains the name of your selected branch. (You can get the merge thing from ${{...}} w/ some amount of effort)

github.event.pull_request.number should be the pr number
github.event.pull_request.merge_commit_sha might get you the sha you want (I don’t actively look at these things)

If your fork isn’t connected, then you’d actually have to do a push. If they’re all private repos, I’d probably be fine w/ a private connected fork and not worry about how to push.

If I understand correctly, this will create a branch “deploy-PR-5” in my forked repo.

With octokit, is there a way for me to take a sha/commit that exists in my forked repo, and push it to my origin repo? This way the commit will exist in the origin and then I can just deploy from the origin…

Any ideas?

This might do what you want:

await octokit.request('POST /repos/{owner}/{repo}/git/refs', {
  owner: 'fork-org',
  repo: 'myrepo',
  ref: 'deploy-PR-'+ github.event.pull_request.number,
  sha: github.event.pull_request.merge_commit_sha
})

Maybe. You’d want to find someone who actively uses octokit (the Probot slack workspace is a good choice) or some octokit examples…

The thing to understand about forked repositories (as opposed to clones) is they share a common sha space, which means that you can create a ref in a fork to a sha that exists elsewhere in the network w/o doing a push. (It’s a lot of black magic…, but it’s how the /merge stuff works for PRs across forks in the first place.)