Skip to content

Commit

Permalink
Merge pull request #16 from buildkite/catkins/open-pr-for-public-sync
Browse files Browse the repository at this point in the history
Automatically Open PRs when syncing public to private sync
  • Loading branch information
gilesgas authored Sep 18, 2024
2 parents 3990cef + b2f3fea commit 9787312
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 30 deletions.
13 changes: 12 additions & 1 deletion .buildkite/pipeline.sync_public_pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,16 @@ steps:
text: "Pull Request Number"
format: "^[0-9]+$"

- command: "bin/sync-public-pr"
- label: ":git: Sync Public PR to Private Repo"
command: "bin/sync-public-pr"
depends_on: collect_pr_number
plugins:
- docker#v5.11.0:
image: "ruby:3.3-bookworm"
propagate-environment: true
mount-buildkite-agent: true
mount-ssh-agent: true
environment:
- GH_TOKEN
- GH_PRIVATE_REPO
- GH_PUBLIC_REPO
133 changes: 119 additions & 14 deletions bin/sync-public-pr
Original file line number Diff line number Diff line change
@@ -1,24 +1,129 @@
#!/usr/bin/env bash
#!/usr/bin/env ruby

set -euo pipefail
require "net/http"
require "open3"
require "json"

PR_NUMBER=$(buildkite-agent meta-data get "pull_request_number")
TARGET_BRANCH="docs-public-pr-${PR_NUMBER}"
SOURCE_REF="pull/${PR_NUMBER}/head"
# Job params
GH_TOKEN = ENV.fetch("GH_TOKEN")
PUBLIC_GH_REPO = ENV.fetch("PUBLIC_GH_REPO")
PRIVATE_GH_REPO = ENV.fetch("PRIVATE_GH_REPO")

echo "+++ Syncing PR #${PR_NUMBER} to local branch ${TARGET_BRANCH}"
PR_NUMBER = `buildkite-agent meta-data get "pull_request_number"`.strip.to_i
TARGET_BRANCH = "docs-public-pr-#{PR_NUMBER}"

set -x
class GithubClient
def initialize
@client = Net::HTTP.new("api.github.com", 443)
@client.use_ssl = true
end

git fetch "[email protected]:${PUBLIC_GH_REPO}.git" "${SOURCE_REF}:${TARGET_BRANCH}"
def public_pr(pr_number)
# https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#get-a-pull-request
response = @client.get("/repos/#{PUBLIC_GH_REPO}/pulls/#{pr_number}", headers)
puts response
JSON.parse(response.body)
end

git push --force origin "${TARGET_BRANCH}"
def private_pr_for_branch(branch_name)
# https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#list-pull-requests
# note this ignores closed/merged PRs
response = @client.get("/repos/#{PRIVATE_GH_REPO}/pulls?head=buildkite:#{branch_name}", headers)
puts response
JSON.parse(response.body).first
end

CREATE_PR_LINK="https://github.com/${PRIVATE_GH_REPO}/compare/${TARGET_BRANCH}?expand=1"
def open_pr(title:, body:, branch:)
# https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#create-a-pull-request
response = @client.post("/repos/#{PRIVATE_GH_REPO}/pulls", {
title: title,
body: body,
head: branch,
base: "main"
}.to_json, headers)
puts response
JSON.parse(response.body)
end

ANNOTATION_CONTENT="Synced PR ${PUBLIC_GH_REPO}#${PR_NUMBER} to private repo branch \`${TARGET_BRANCH}\`.
private

Open PR: ${CREATE_PR_LINK}
"
def headers
{
"Accept" => "application/vnd.github+json",
"Authorization" => "Bearer #{GH_TOKEN}",
"X-GitHub-Api-Version" =>"2022-11-28"
}
end
end

buildkite-agent annotate --context pr-link --style "info" "${ANNOTATION_CONTENT}"
def write_annotation(content, style: "info")
Open3.capture2("buildkite-agent", "annotate", "--style", style, stdin_data: content)
end

client = GithubClient.new

puts "+++ :git: Syncing #{PUBLIC_GH_REPO} PR ##{PR_NUMBER} to local branch #{TARGET_BRANCH}"

# pull the magic PR ref from the public repo into local docs-public-pr-<PR_NUMBER> branch
source_ref = "pull/#{PR_NUMBER}/head"

# https://git-scm.com/docs/git-fetch see docs on refspec format
refspec = "#{source_ref}:#{TARGET_BRANCH}"

`git fetch --force "[email protected]:#{PUBLIC_GH_REPO}.git" "#{refspec}"`


puts "+++ :git: Pushing branch #{TARGET_BRANCH} to #{PRIVATE_GH_REPO}"

`git push --force origin "#{TARGET_BRANCH}"`

puts "+++ :github: Fetching original PR #{PUBLIC_GH_REPO} ##{PR_NUMBER}"

public_pr = client.public_pr(PR_NUMBER)

puts "--- :octocat: Checking for PR for #{TARGET_BRANCH} in #{PRIVATE_GH_REPO}"

private_pr = client.private_pr_for_branch(TARGET_BRANCH)
puts private_pr

if private_pr
annotation_content = <<~ANNOTATION
:open-pr: Re-synced PR #{PUBLIC_GH_REPO} ##{PR_NUMBER} to private repo branch `#{TARGET_BRANCH}`.
Original PR: https://github.com/#{PUBLIC_GH_REPO}/pull/#{PR_NUMBER}
Private PR: #{private_pr["html_url"]} :robot_face:
ANNOTATION

write_annotation(annotation_content)
exit 0
end

puts "+++ :open-pr: Creating PR for #{TARGET_BRANCH} in #{PRIVATE_GH_REPO}"

pr_description = <<-PR_DESCRIPTION
:robot: Synced by #{ENV["BUILDKITE_BUILD_URL"]} :robot:
_Note_: The original public PR will automatically close when this PR is merged. If there are additional changes to sync from the public PR, re-run the [Docs (Sync public PR)](https://buildkite.com/buildkite/docs-sync-public-pr) Pipeline.
---
## [docs##{PR_NUMBER}](#{public_pr["html_url"]}): #{public_pr["title"]}
Opened by @#{public_pr.dig("user", "login")}
#{public_pr["body"]}
PR_DESCRIPTION

private_pr = client.open_pr(title: public_pr["title"], body: pr_description, branch: TARGET_BRANCH)

annotation_content = <<~ANNOTATION
:open-pr: Synced PR #{PUBLIC_GH_REPO} ##{PR_NUMBER} to private repo branch `#{TARGET_BRANCH}`.
Original PR: #{public_pr["html_url"]} - #{public_pr["title"]} by @#{public_pr.dig("user", "login")}
Private PR: #{private_pr["html_url"]} :robot_face:
ANNOTATION

write_annotation(annotation_content)
52 changes: 37 additions & 15 deletions bin/utils.sh
Original file line number Diff line number Diff line change
@@ -1,53 +1,75 @@
#!/bin/bash

# Find pull request number for a given branch.
#
# Why not `$BUILDKITE_PULL_REQUEST`? That env variable is only set
# for builds triggered via Github and it's possible previews are
# manually triggered via the API or Buildkite Dashboard.

# ensure GH_REPO env var is present
if [ -z "$GH_REPO" ]; then
echo "GH_REPO env var is required"
exit 1
fi

API_BASE_PATH="https://api.github.com/repos/${GH_REPO}"

# Find pull request number for a given branch.
#
# Why not `$BUILDKITE_PULL_REQUEST`? That env variable is only set
# for builds triggered via Github and it's possible previews are
# manually triggered via the API or Buildkite Dashboard.
function get_branch_pull_request_number() {
local branch=$1

curl -L \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GH_TOKEN" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/${GH_REPO}/pulls\?head=buildkite\:$1 \
${API_BASE_PATH}/pulls\?head=buildkite\:${branch} \
| jq ".[0].number | select (.!=null)"
}

function find_github_comment() {
local pr_number="$1"
local msg="$2"

curl -L \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GH_TOKEN" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/${GH_REPO}/issues/$1/comments \
| jq --arg msg "$2" '.[] | select(.body==$msg)'
${API_BASE_PATH}/issues/${pr_number}/comments \
| jq --arg msg "$msg" '.[] | select(.body==$msg)'
}

function post_github_comment() {
local pr_number=$1
local msg=$2

curl -L \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GH_TOKEN" \
-H "X-GitHub-Api-Version: 2022-11-28" \
--data "{\"body\":\"$2\"}" \
https://api.github.com/repos/${GH_REPO}/issues/$1/comments
--data "{\"body\":\"${msg}\"}" \
${API_BASE_PATH}/issues/${pr_number}/comments
}

function create_pull_request() {
local title="$1"
local body="$2"
local branch="$3"

local request_body=$(
jq --null-input \
--compact-output \
--arg title "$title" \
--arg body "$body" \
--arg head "$branch" \
--arg base "main" \
'{title: $title, body: $body, head: $head, base: $base}'
)

curl -L \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GH_TOKEN" \
-H "Authorization Bearer $GH_TOKEN" \
-H "X-GitHub-Api-Version: 2022-11-28" \
--data "{\"title\":\"$1\", \"body\":\"$2\", \"head\":\"$BRANCH\", \"base\":\"main\"}" \
"https://api.github.com/repos/${GH_REPO}/pulls"
--json "$request_body" \
"${API_BASE_PATH}/pulls"
}

function netlify_preview_id() {
Expand Down

0 comments on commit 9787312

Please sign in to comment.