generated from int128/typescript-actions-monorepo
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add bootstrap-pull-request action (#1167)
- Loading branch information
Showing
18 changed files
with
876 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,92 @@ | ||
name: bootstrap-pull-request | ||
|
||
on: | ||
pull_request: | ||
paths: | ||
- bootstrap-pull-request/** | ||
- '*.json' | ||
- .github/workflows/bootstrap-pull-request.yaml | ||
push: | ||
branches: | ||
- main | ||
paths: | ||
- bootstrap-pull-request/** | ||
- '*.json' | ||
- .github/workflows/bootstrap-pull-request.yaml | ||
|
||
defaults: | ||
run: | ||
working-directory: bootstrap-pull-request | ||
|
||
jobs: | ||
test: | ||
runs-on: ubuntu-latest | ||
timeout-minutes: 10 | ||
steps: | ||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 | ||
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 | ||
with: | ||
node-version: 20 | ||
cache: yarn | ||
- run: yarn | ||
- run: yarn test | ||
|
||
e2e-test: | ||
runs-on: ubuntu-latest | ||
timeout-minutes: 10 | ||
steps: | ||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 | ||
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 | ||
with: | ||
node-version: 20 | ||
cache: yarn | ||
- run: yarn | ||
- run: yarn build | ||
- run: yarn package | ||
|
||
- run: | | ||
git config --global user.email '[email protected]' | ||
git config --global user.name 'github-actions' | ||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 | ||
with: | ||
ref: ${{ github.head_ref }} # avoid "shallow update not allowed" error | ||
path: prebuilt-branch | ||
- name: Set up an prebuilt branch | ||
working-directory: prebuilt-branch | ||
run: | | ||
mkdir -vp services/a | ||
mkdir -vp services/b | ||
touch services/a/generated.yaml | ||
touch services/b/generated.yaml | ||
git add . | ||
git commit -m "Add prebuilt branch for e2e-test of ${GITHUB_REF}" | ||
git push origin "HEAD:refs/heads/prebuilt/monorepo-deploy-actions/overlay-${{ github.run_id }}" | ||
- uses: ./bootstrap-pull-request | ||
with: | ||
overlay: overlay-${{ github.run_id }} | ||
namespace: pr-${{ github.event.number }} | ||
destination-repository: ${{ github.repository }} | ||
namespace-manifest: bootstrap-pull-request/tests/fixtures/namespace.yaml | ||
substitute-variables: NAMESPACE=pr-${{ github.event.number }} | ||
|
||
# the action should be idempotent | ||
- uses: ./bootstrap-pull-request | ||
with: | ||
overlay: overlay-${{ github.run_id }} | ||
namespace: pr-${{ github.event.number }} | ||
destination-repository: ${{ github.repository }} | ||
namespace-manifest: bootstrap-pull-request/tests/fixtures/namespace.yaml | ||
substitute-variables: NAMESPACE=pr-${{ github.event.number }} | ||
|
||
- name: Clean up the namespace branch | ||
continue-on-error: true | ||
if: always() | ||
run: | | ||
git push origin --delete "refs/heads/ns/monorepo-deploy-actions/overlay-${{ github.run_id }}/pr-${{ github.event.number }}" | ||
- name: Clean up the prebuilt branch | ||
continue-on-error: true | ||
if: always() | ||
run: | | ||
git push origin --delete "refs/heads/prebuilt/monorepo-deploy-actions/overlay-${{ github.run_id }}" |
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,137 @@ | ||
# bootstrap-pull-request [![bootstrap-pull-request](https://github.com/quipper/monorepo-deploy-actions/actions/workflows/bootstrap-pull-request.yaml/badge.svg)](https://github.com/quipper/monorepo-deploy-actions/actions/workflows/bootstrap-pull-request.yaml) | ||
|
||
This is an action to bootstrap the pull request namespace. | ||
When a pull request is created or updated, this action copies the service manifests from the prebuilt branch. | ||
|
||
```mermaid | ||
graph LR | ||
subgraph Source repository | ||
SourceNamespaceManifest[Namespace manifest] | ||
end | ||
subgraph Destination repository | ||
subgraph Prebuilt branch | ||
PrebuiltServiceManifest[Service manifest] | ||
end | ||
subgraph Namespace branch | ||
ApplicationManifest[Application manifest] | ||
ServiceManifest[Service manifest] | ||
NamespaceManifest[Namespace manifest] | ||
end | ||
end | ||
PrebuiltServiceManifest --Copy--> ServiceManifest | ||
SourceNamespaceManifest --Copy--> NamespaceManifest | ||
``` | ||
|
||
## Getting Started | ||
|
||
To bootstrap the pull request namespace, | ||
|
||
```yaml | ||
name: pr-namespace / bootstrap | ||
|
||
on: | ||
pull_request: | ||
|
||
jobs: | ||
bootstrap-pull-request: | ||
runs-on: ubuntu-latest | ||
timeout-minutes: 10 | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- uses: quipper/monorepo-deploy-actions/bootstrap-pull-request@v1 | ||
with: | ||
overlay: pr | ||
namespace: pr-${{ github.event.number }} | ||
destination-repository: octocat/generated-manifests | ||
destination-repository-token: ${{ steps.destination-repository-github-app.outputs.token }} | ||
namespace-manifest: deploy-config/overlays/pr/namespace.yaml | ||
substitute-variables: | | ||
NAMESPACE=pr-${{ github.event.number }} | ||
``` | ||
This action creates a namespace branch into the destination repository. | ||
``` | ||
ns/${source-repository}/${overlay}/${namespace-prefix}${pull-request-number} | ||
``` | ||
|
||
It creates the following directory structure. | ||
|
||
``` | ||
. | ||
├── applications | ||
| ├── namespace.yaml | ||
| └── ${namespace}--${service}.yaml | ||
└── services | ||
└── ${service} | ||
└── generated.yaml | ||
``` | ||
|
||
It assumes that the below name of prebuilt branch exists in the destination repository. | ||
|
||
``` | ||
prebuilt/${source-repository}/${overlay} | ||
``` | ||
|
||
It bootstraps the namespace branch by the following steps: | ||
|
||
- Copy the services from prebuilt branch. | ||
- Write the namespace manifest | ||
|
||
### Copy the services from prebuilt branch | ||
|
||
This action copies the services from prebuilt branch to the namespace branch. | ||
|
||
For example, if the prebuilt branch has 2 services `backend` and `frontend`, | ||
the namespace branch will be the below structure. | ||
|
||
``` | ||
. | ||
├── applications | ||
| ├── pr-123--backend.yaml | ||
| └── pr-123--frontend.yaml | ||
└── services | ||
├── backend | ||
| └── generated.yaml | ||
└── frontend | ||
└── generated.yaml | ||
``` | ||
|
||
All placeholders will be replaced during copying the service manifests. | ||
For example, if `NAMESPACE=pr-123` is given by `substitute-variables` input, | ||
this action will replace `${NAMESPACE}` with `pr-123`. | ||
|
||
If a service was pushed by `git-push-service` action, | ||
this action does not overwrite it. | ||
|
||
**Case 1**: If a service is not changed in a pull request, | ||
|
||
1. When a pull request is created, this action copies the service from prebuilt branch. | ||
1. When the pull request is updated, this action copies the service from prebuilt branch. | ||
This is needed to follow the latest change of prebuilt branch. | ||
|
||
**Case 2**: If a service is changed in a pull request, | ||
|
||
1. When a pull request is created, | ||
- This action copies the service from prebuilt branch. | ||
- `git-push-service` action overwrites the service. | ||
1. When the pull request is synchronized, | ||
- This action does not overwrite the service. | ||
|
||
### Write the namespace manifest | ||
|
||
This action copies the namespace manifest to path `/applications/namespace.yaml` in the namespace branch. | ||
|
||
``` | ||
. | ||
└── applications | ||
└── namespace.yaml | ||
``` | ||
|
||
All placeholders will be replaced during copying the namespace manifest. | ||
For example, if `NAMESPACE=pr-123` is given by `substitute-variables` input, | ||
this action will replace `${NAMESPACE}` with `pr-123`. | ||
|
||
## Specification | ||
|
||
See [action.yaml](action.yaml). |
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,31 @@ | ||
name: bootstrap-pull-request | ||
description: bootstrap the pull request namespace | ||
|
||
inputs: | ||
overlay: | ||
description: Name of overlay | ||
required: true | ||
namespace: | ||
description: Name of namespace | ||
required: true | ||
source-repository: | ||
description: Source repository | ||
required: true | ||
default: ${{ github.repository }} | ||
destination-repository: | ||
description: Destination repository | ||
required: true | ||
destination-repository-token: | ||
description: GitHub token for destination repository | ||
required: true | ||
default: ${{ github.token }} | ||
namespace-manifest: | ||
description: Path to namespace manifest (optional) | ||
required: false | ||
substitute-variables: | ||
description: Pairs of key=value to substitute the prebuilt manifests (multiline) | ||
required: false | ||
|
||
runs: | ||
using: 'node20' | ||
main: 'dist/index.js' |
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,7 @@ | ||
module.exports = { | ||
preset: 'ts-jest', | ||
clearMocks: true, | ||
testEnvironment: 'node', | ||
testMatch: ['**/*.test.ts'], | ||
verbose: true, | ||
} |
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,15 @@ | ||
{ | ||
"name": "bootstrap-pull-request", | ||
"version": "0.0.0", | ||
"private": true, | ||
"main": "lib/src/main.js", | ||
"scripts": { | ||
"build": "tsc", | ||
"package": "ncc build --source-map --license licenses.txt", | ||
"test": "jest", | ||
"ci:package": "yarn build && yarn package" | ||
}, | ||
"dependencies": { | ||
"@actions/core": "1.10.1" | ||
} | ||
} |
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,80 @@ | ||
import * as core from '@actions/core' | ||
import * as exec from '@actions/exec' | ||
import * as os from 'os' | ||
import * as path from 'path' | ||
import { promises as fs } from 'fs' | ||
|
||
type CheckoutOptions = { | ||
repository: string | ||
branch: string | ||
token: string | ||
} | ||
|
||
export const checkout = async (opts: CheckoutOptions) => { | ||
const cwd = await fs.mkdtemp(path.join(process.env.RUNNER_TEMP || os.tmpdir(), 'git-')) | ||
core.info(`Cloning ${opts.repository} into ${cwd}`) | ||
await exec.exec('git', ['version'], { cwd }) | ||
await exec.exec('git', ['init', '--initial-branch', opts.branch], { cwd }) | ||
await exec.exec('git', ['config', '--local', 'gc.auto', '0'], { cwd }) | ||
await exec.exec('git', ['remote', 'add', 'origin', `https://github.com/${opts.repository}`], { cwd }) | ||
const credentials = Buffer.from(`x-access-token:${opts.token}`).toString('base64') | ||
core.setSecret(credentials) | ||
await exec.exec( | ||
'git', | ||
['config', '--local', 'http.https://github.com/.extraheader', `AUTHORIZATION: basic ${credentials}`], | ||
{ cwd }, | ||
) | ||
await exec.exec( | ||
'git', | ||
['fetch', '--no-tags', '--depth=1', 'origin', `+refs/heads/${opts.branch}:refs/remotes/origin/${opts.branch}`], | ||
{ cwd }, | ||
) | ||
await exec.exec('git', ['checkout', opts.branch], { cwd }) | ||
return cwd | ||
} | ||
|
||
export const checkoutOrInitRepository = async (opts: CheckoutOptions) => { | ||
const cwd = await fs.mkdtemp(path.join(process.env.RUNNER_TEMP || os.tmpdir(), 'git-')) | ||
core.info(`Cloning ${opts.repository} into ${cwd}`) | ||
await exec.exec('git', ['version'], { cwd }) | ||
await exec.exec('git', ['init', '--initial-branch', opts.branch], { cwd }) | ||
await exec.exec('git', ['config', '--local', 'gc.auto', '0'], { cwd }) | ||
await exec.exec('git', ['remote', 'add', 'origin', `https://github.com/${opts.repository}`], { cwd }) | ||
const credentials = Buffer.from(`x-access-token:${opts.token}`).toString('base64') | ||
core.setSecret(credentials) | ||
await exec.exec( | ||
'git', | ||
['config', '--local', 'http.https://github.com/.extraheader', `AUTHORIZATION: basic ${credentials}`], | ||
{ cwd }, | ||
) | ||
const code = await exec.exec( | ||
'git', | ||
['fetch', '--no-tags', '--depth=1', 'origin', `+refs/heads/${opts.branch}:refs/remotes/origin/${opts.branch}`], | ||
{ cwd, ignoreReturnCode: true }, | ||
) | ||
if (code === 0) { | ||
await exec.exec('git', ['checkout', opts.branch], { cwd }) | ||
return cwd | ||
} | ||
// If the remote branch does not exist, set up the tracking branch. | ||
await exec.exec('git', ['config', '--local', `branch.${opts.branch}.remote`, 'origin'], { cwd }) | ||
await exec.exec('git', ['config', '--local', `branch.${opts.branch}.merge`, `refs/heads/${opts.branch}`], { cwd }) | ||
return cwd | ||
} | ||
|
||
export const status = async (cwd: string): Promise<string> => { | ||
const output = await exec.getExecOutput('git', ['status', '--porcelain'], { cwd }) | ||
return output.stdout.trim() | ||
} | ||
|
||
export const commit = async (cwd: string, message: string): Promise<void> => { | ||
await exec.exec('git', ['add', '.'], { cwd }) | ||
await exec.exec('git', ['config', 'user.email', '41898282+github-actions[bot]@users.noreply.github.com'], { cwd }) | ||
await exec.exec('git', ['config', 'user.name', 'github-actions[bot]'], { cwd }) | ||
await exec.exec('git', ['commit', '-m', message], { cwd }) | ||
await exec.exec('git', ['rev-parse', 'HEAD'], { cwd }) | ||
} | ||
|
||
export const pushByFastForward = async (cwd: string): Promise<number> => { | ||
return await exec.exec('git', ['push', 'origin'], { cwd, ignoreReturnCode: true }) | ||
} |
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,19 @@ | ||
import * as core from '@actions/core' | ||
import { run } from './run' | ||
|
||
const main = async (): Promise<void> => { | ||
await run({ | ||
overlay: core.getInput('overlay', { required: true }), | ||
namespace: core.getInput('namespace', { required: true }), | ||
sourceRepository: core.getInput('source-repository', { required: true }), | ||
destinationRepository: core.getInput('destination-repository', { required: true }), | ||
destinationRepositoryToken: core.getInput('destination-repository-token', { required: true }), | ||
namespaceManifest: core.getInput('namespace-manifest') || undefined, | ||
substituteVariables: core.getMultilineInput('substitute-variables'), | ||
}) | ||
} | ||
|
||
main().catch((e: Error) => { | ||
core.setFailed(e) | ||
console.error(e) | ||
}) |
Oops, something went wrong.