-
-
Notifications
You must be signed in to change notification settings - Fork 91
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: post a pr to bcr on successful release (#97)
* feat: post a pr to bcr on successful release * Change to GH app based auth Following instructions at https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#authenticating-with-github-app-generated-tokens Co-authored-by: Alex Eagle <[email protected]>
- Loading branch information
Showing
5 changed files
with
323 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
# GitHub Actions workflow to create a pull request to update the bazel central | ||
# registry after a successful release. | ||
# | ||
# Usage: | ||
# 1. Fork bazelbuild/bazel-central-registry to your org or personal account. | ||
# 2. Set the workflow env variables below (see instructions for each variable). | ||
# 3. Update the following files in .github/workflows/bcr to be relevant for your | ||
# project: | ||
# - presubmit.yml | ||
# - metadata.template.json | ||
# - source.template.json | ||
# See https://docs.bazel.build/versions/main/bzlmod.html#bazel-central-registry | ||
# for more information. | ||
# 4. A PR will be posted from your fork to bazelbuild/bazel-central-registry | ||
# with an updated module entry after a successful release. | ||
|
||
name: BCR | ||
|
||
on: | ||
release: | ||
types: [published] | ||
|
||
env: | ||
# Bazel central registry to post a pull request to. | ||
BCR: aspect-build/bazel-central-registry | ||
|
||
# Fork of the bazel central registery to push the pull request branch to | ||
# BCR_FORK: aspect-build/bazel-central-registry | ||
# TODO: disable this for bazel-lib as we always want to push to our own fork, | ||
# but re-enable for later plugin | ||
|
||
jobs: | ||
bcr-pull-request: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/setup-node@v2 | ||
with: | ||
node-version: "14" | ||
- name: Get the tag | ||
id: get_tag | ||
run: echo ::set-output name=TAG::${GITHUB_REF#refs/tags/} | ||
- name: Checkout this repo to access scripts | ||
uses: actions/checkout@v3 | ||
- name: Checkout the released version of this repo | ||
uses: actions/checkout@v3 | ||
with: | ||
ref: ${{ env.GITHUB_REF }} | ||
path: project | ||
- name: Checkout bcr | ||
uses: actions/checkout@v3 | ||
with: | ||
repository: ${{ env.BCR }} | ||
path: bcr | ||
- uses: tibdex/github-app-token@v1 | ||
id: generate-token | ||
with: | ||
app_id: ${{ secrets.BCR_APP_ID }} | ||
private_key: ${{ secrets.BCR_APP_PRIVATE_KEY }} | ||
- name: Create bcr entry | ||
run: node .github/workflows/create-bcr-entry.mjs project bcr $GITHUB_REPOSITORY ${{ steps.get_tag.outputs.TAG }} | ||
- name: Create Pull Request | ||
id: post_pr | ||
uses: peter-evans/create-pull-request@v4 | ||
with: | ||
token: ${{ steps.generate-token.outputs.token }} | ||
path: bcr | ||
# push-to-fork: ${{ env.BCR_FORK }} | ||
commit-message: ${{ github.repository }}@${{ steps.get_tag.outputs.TAG }} | ||
branch: ${{ github.repository }}@${{ steps.get_tag.outputs.TAG }} | ||
title: ${{ github.repository }}@${{ steps.get_tag.outputs.TAG }} | ||
body: | | ||
[Automated] Publish ${{ github.repository }}@${{ steps.get_tag.outputs.TAG }} to bcr by [bazel-contrib](https://github.com/bazel-contrib). | ||
Manual checks: | ||
- [ ] I have checked that the version is correct. | ||
- [ ] I have checked that the compatibility_level in MODULE.bazel is correct. | ||
- name: Echo PR url | ||
run: echo ${{ steps.post_pr.outputs.pull-request-url }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"homepage": "https://docs.aspect.dev/bazel-lib", | ||
"maintainers": [ | ||
{ | ||
"email": "[email protected]", | ||
"github": "aspect-build", | ||
"name": "Aspect team" | ||
} | ||
], | ||
"versions": [], | ||
"yanked_versions": {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
build_targets: &build_targets | ||
- "@aspect_bazel_lib//lib/tests:expand_template_test" | ||
|
||
platforms: | ||
centos7: | ||
build_targets: *build_targets | ||
debian10: | ||
build_targets: *build_targets | ||
macos: | ||
build_targets: *build_targets | ||
ubuntu2004: | ||
build_targets: *build_targets | ||
windows: | ||
build_targets: *build_targets |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"integrity": "**leave this alone**", | ||
"strip_prefix": "{REPO}-{VERSION}", | ||
"url": "https://github.com/{OWNER}/{REPO}/archive/{TAG}.tar.gz" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
import crypto from "crypto"; | ||
import { | ||
readFileSync, | ||
mkdirSync, | ||
existsSync, | ||
copyFileSync, | ||
writeFileSync, | ||
appendFileSync, | ||
} from "fs"; | ||
import https from "https"; | ||
import { resolve } from "path"; | ||
|
||
/** | ||
* Create a bcr entry for a new version of this repository. | ||
* | ||
* Usage: create-bcr-entry [project_path] [bcr_path] [owner_slash_repo] [tag] | ||
* | ||
* project_path: path to the project's repository; should contain a | ||
* root level MODULE.bazel file and a .github/workflows/bcr folder | ||
* with templated bcr entry files. | ||
* bcr_path: path to the bcr repository | ||
* owner_slash_repo: the github owner/repository name of the project | ||
* tag: the github tag for this version, e.g., "v1.0.0" or "1.0.0" | ||
* | ||
*/ | ||
async function main(argv) { | ||
if (argv.length !== 4) { | ||
console.error( | ||
"usage: create-bcr-entry [project_path] [bcr_path] [owner_slash_repo] [tag]" | ||
); | ||
process.exit(1); | ||
} | ||
|
||
const projectPath = argv[0]; | ||
const bcrPath = argv[1]; | ||
const ownerSlashRepo = argv[2]; | ||
const tag = argv[3]; | ||
const version = getVersionFromTag(tag); | ||
|
||
const moduleName = getModuleName(resolve(projectPath, "MODULE.bazel")); | ||
const bcrTemplatesPath = resolve(projectPath, ".github/workflows/bcr"); | ||
const bcrEntryPath = resolve(bcrPath, "modules", moduleName); | ||
const bcrVersionEntryPath = resolve(bcrEntryPath, version); | ||
|
||
if (!existsSync(bcrEntryPath)) { | ||
mkdirSync(bcrEntryPath); | ||
} | ||
|
||
updateMetadataFile( | ||
resolve(bcrTemplatesPath, "metadata.template.json"), | ||
resolve(bcrEntryPath, "metadata.json"), | ||
version | ||
); | ||
|
||
mkdirSync(bcrVersionEntryPath); | ||
|
||
stampModuleFile( | ||
resolve(projectPath, "MODULE.bazel"), | ||
resolve(bcrVersionEntryPath, "MODULE.bazel"), | ||
version | ||
); | ||
|
||
await stampSourceFile( | ||
resolve(bcrTemplatesPath, "source.template.json"), | ||
resolve(bcrVersionEntryPath, "source.json"), | ||
ownerSlashRepo, | ||
version, | ||
tag | ||
); | ||
|
||
// Copy over the presubmit file | ||
copyFileSync( | ||
resolve(bcrTemplatesPath, "presubmit.yml"), | ||
resolve(bcrVersionEntryPath, "presubmit.yml") | ||
); | ||
} | ||
|
||
function getModuleName(modulePath) { | ||
const moduleContent = readFileSyncOrFail( | ||
modulePath, | ||
"Cannot find MODULE.bazel; bzlmod requires this file at the root of your workspace." | ||
); | ||
|
||
const regex = /module\(.*?name\s*=\s*"(\w+)"/s; | ||
const match = moduleContent.match(regex); | ||
if (match) { | ||
return match[1]; | ||
} | ||
throw new Error("Could not parse module name from module file"); | ||
} | ||
|
||
function updateMetadataFile(sourcePath, destPath, version) { | ||
let publishedVersions = []; | ||
if (existsSync(destPath)) { | ||
const existingMetadata = JSON.parse( | ||
readFileSync(destPath, { encoding: "utf-8" }) | ||
); | ||
publishedVersions = existingMetadata.versions; | ||
} | ||
|
||
if (publishedVersions.includes(version)) { | ||
console.error(`Version ${version} is already published to this registry`); | ||
process.exit(1); | ||
} | ||
|
||
const metadata = JSON.parse( | ||
readFileSyncOrFail(sourcePath), | ||
`Cannot find metadata template ${sourcePath}; did you forget to create it?` | ||
); | ||
metadata.versions = [...publishedVersions, version]; | ||
metadata.versions.sort(); | ||
|
||
writeFileSync(destPath, JSON.stringify(metadata, null, 4) + "\n"); | ||
} | ||
|
||
function stampModuleFile(sourcePath, destPath, version) { | ||
const module = readFileSyncOrFail( | ||
sourcePath, | ||
"Cannot find MODULE.bazel; bzlmod requires this file at the root of your workspace." | ||
); | ||
|
||
const stampedModule = module.replace( | ||
/(^.*?module\(.*?version\s*=\s*")[\w.]+(".*$)/s, | ||
`$1${version}$2` | ||
); | ||
|
||
writeFileSync(destPath, stampedModule, { | ||
encoding: "utf-8", | ||
}); | ||
} | ||
|
||
async function stampSourceFile( | ||
sourcePath, | ||
destPath, | ||
ownerSlashRepo, | ||
version, | ||
tag | ||
) { | ||
const owner = ownerSlashRepo.substring(0, ownerSlashRepo.indexOf("/")); | ||
const repo = ownerSlashRepo.substring(ownerSlashRepo.indexOf("/") + 1); | ||
|
||
// Substitute variables into source.json | ||
const sourceContent = readFileSyncOrFail( | ||
sourcePath, | ||
`Cannot find source template ${sourcePath}; did you forget to create it?` | ||
); | ||
const substituted = sourceContent | ||
.replace(/{REPO}/g, repo) | ||
.replace(/{OWNER}/g, owner) | ||
.replace(/{VERSION}/g, version) | ||
.replace(/{TAG}/g, tag); | ||
|
||
// Compute the integrity hash | ||
const sourceJson = JSON.parse(substituted); | ||
const filename = sourceJson.url.substring( | ||
sourceJson.url.lastIndexOf("/") + 1 | ||
); | ||
|
||
await download(sourceJson.url, filename); | ||
|
||
const hash = crypto.createHash("sha256"); | ||
hash.update(readFileSync(filename)); | ||
const digest = hash.digest("base64"); | ||
sourceJson.integrity = `sha256-${digest}`; | ||
|
||
writeFileSync(destPath, JSON.stringify(sourceJson, undefined, 4), { | ||
encoding: "utf-8", | ||
}); | ||
} | ||
|
||
function getVersionFromTag(version) { | ||
if (version.startsWith("v")) { | ||
return version.substring(1); | ||
} | ||
} | ||
|
||
function download(url, dest) { | ||
return new Promise((resolve, reject) => { | ||
https | ||
.get(url, (response) => { | ||
response.on("data", (chunk) => { | ||
appendFileSync(dest, chunk); | ||
}); | ||
response.on("end", () => { | ||
resolve(); | ||
}); | ||
}) | ||
.on("error", (err) => { | ||
reject(new Error(err.message)); | ||
}); | ||
}); | ||
} | ||
|
||
function readFileSyncOrFail(filename, notExistsMsg) { | ||
try { | ||
return readFileSync(filename, { encoding: "utf-8" }); | ||
} catch (error) { | ||
if (error.code === "ENOENT") { | ||
console.error(notExistsMsg); | ||
process.exit(1); | ||
} | ||
throw error; | ||
} | ||
} | ||
|
||
(async () => { | ||
const argv = process.argv.slice(2); | ||
try { | ||
await main(argv); | ||
} catch (error) { | ||
console.error(error); | ||
process.exit(1); | ||
} | ||
})(); |