Skip to content

Commit af0e3ea

Browse files
authored
Add get-service-versions action & add several parameters for bootstrap-pull-request action to support partial service deployments (#1801)
* feat(get-service-versions) Initial implementation * feat(bootstrap-pull-request) Add `excluded-services` input parameter * feat(bootstrap-pull-request) Add `invert-exclude-services` input parameter * remove unneeded retryExponential * use npm instead of corepack * remove redundant code
1 parent 682a522 commit af0e3ea

File tree

20 files changed

+606
-1
lines changed

20 files changed

+606
-1
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
name: get-service-versions
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- get-service-versions/**
7+
- '*.json'
8+
- '*.yaml'
9+
- .github/workflows/get-service-versions.yaml
10+
push:
11+
branches:
12+
- main
13+
paths:
14+
- get-service-versions/**
15+
- '*.json'
16+
- '*.yaml'
17+
- .github/workflows/get-service-versions.yaml
18+
19+
defaults:
20+
run:
21+
working-directory: get-service-versions
22+
23+
jobs:
24+
test:
25+
runs-on: ubuntu-latest
26+
timeout-minutes: 10
27+
steps:
28+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
29+
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
30+
with:
31+
node-version: 20
32+
- run: npm install -g pnpm@latest-10
33+
- run: pnpm i
34+
- run: pnpm test
35+
36+
e2e-test:
37+
runs-on: ubuntu-latest
38+
timeout-minutes: 10
39+
steps:
40+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
41+
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
42+
with:
43+
node-version: 20
44+
- run: npm install -g pnpm@latest-10
45+
- run: pnpm i
46+
- run: pnpm build
47+
48+
- run: |
49+
git config --global user.email '[email protected]'
50+
git config --global user.name 'github-actions'
51+
52+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
53+
with:
54+
ref: ${{ github.head_ref }} # avoid "shallow update not allowed" error
55+
path: overlay-branch
56+
- name: Set up an overlay branch
57+
working-directory: overlay-branch
58+
run: |
59+
cp -av "$GITHUB_WORKSPACE/get-service-versions/tests/fixtures/." .
60+
git add .
61+
git commit -m "Add overlay branch for e2e-test of ${GITHUB_REF}"
62+
git push origin "HEAD:refs/heads/ns/monorepo-deploy-actions/overlay-${{ github.run_id }}/pr-${{ github.event.number }}"
63+
64+
- uses: ./get-service-versions
65+
id: get-service-versions
66+
with:
67+
overlay: overlay-${{ github.run_id }}
68+
namespace: pr-${{ github.event.number }}
69+
destination-repository: ${{ github.repository }}
70+
71+
- name: Check the service versions
72+
run: |
73+
set -x
74+
75+
echo '${{ steps.get-service-versions.outputs.application-versions }}' > service_versions.json
76+
cat service_versions.json | jq "."
77+
78+
# assertion
79+
[ "$(cat service_versions.json | jq -r '.[0].service')" = "a" ]
80+
[ "$(cat service_versions.json | jq -r '.[0].action')" = "git-push-service" ]
81+
[ "$(cat service_versions.json | jq -r '.[0].headRef')" = "main" ]
82+
[ "$(cat service_versions.json | jq -r '.[0].headSha')" = "main-branch-sha" ]
83+
- name: Clean up the overlay branch
84+
continue-on-error: true
85+
if: always()
86+
run: |
87+
git push origin --delete "refs/heads/ns/monorepo-deploy-actions/overlay-${{ github.run_id }}/pr-${{ github.event.number }}"

bootstrap-pull-request/action.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ inputs:
3131
current-head-sha:
3232
description: SHA of current head commit (For internal use)
3333
default: ${{ github.event.pull_request.head.sha || github.sha }}
34+
exclude-services:
35+
description: List of services to exclude from the overlay (multiline)
36+
required: false
37+
invert-exclude-services:
38+
description: Invert the exclude list
39+
required: false
40+
default: "false"
3441

3542
outputs:
3643
services:

bootstrap-pull-request/src/main.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ const main = async (): Promise<void> => {
1212
namespaceManifest: core.getInput('namespace-manifest') || undefined,
1313
substituteVariables: core.getMultilineInput('substitute-variables'),
1414
currentHeadSha: core.getInput('current-head-sha', { required: true }),
15+
excludeServices: core.getMultilineInput('exclude-services'),
16+
invertExcludeServices: core.getBooleanInput('invert-exclude-services') || false,
1517
})
1618
core.setOutput('services', JSON.stringify(outputs.services))
1719
}

bootstrap-pull-request/src/prebuilt.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ type Inputs = {
1616
prebuiltDirectory: string
1717
namespaceDirectory: string
1818
substituteVariables: Map<string, string>
19+
excludeServices: string[]
20+
invertExcludeServices: boolean
1921
}
2022

2123
export type Service = {
@@ -49,13 +51,27 @@ const deleteOutdatedApplicationManifests = async (inputs: Inputs): Promise<void>
4951
matchDirectories: false,
5052
})
5153
for await (const applicationManifestPath of applicationManifestGlob.globGenerator()) {
52-
await deleteOutdatedApplicationManifest(applicationManifestPath, inputs.currentHeadSha)
54+
await deleteOutdatedApplicationManifest(
55+
applicationManifestPath,
56+
inputs.currentHeadSha,
57+
inputs.excludeServices,
58+
inputs.invertExcludeServices,
59+
)
60+
}
61+
}
62+
63+
const shouldServiceExcluded = (service: string, excludeServices: string[], invertExcludeServices: boolean): boolean => {
64+
if (invertExcludeServices) {
65+
return !excludeServices.includes(service)
5366
}
67+
return excludeServices.includes(service)
5468
}
5569

5670
const deleteOutdatedApplicationManifest = async (
5771
applicationManifestPath: string,
5872
currentHeadSha: string,
73+
excludeServices: string[],
74+
invertExcludeServices: boolean,
5975
): Promise<void> => {
6076
const application = await parseApplicationManifest(applicationManifestPath)
6177
if (application instanceof Error) {
@@ -65,6 +81,13 @@ const deleteOutdatedApplicationManifest = async (
6581
return
6682
}
6783

84+
const service = path.basename(application.spec.source.path)
85+
86+
if (shouldServiceExcluded(service, excludeServices, invertExcludeServices)) {
87+
core.info(`Preserving the application manifest: ${applicationManifestPath} because the service is excluded`)
88+
return
89+
}
90+
6891
// bootstrap-pull-request action needs to be run after git-push-service action.
6992
// See https://github.com/quipper/monorepo-deploy-actions/pull/1763 for the details.
7093
if (application.metadata.annotations['github.action'] === 'git-push-service') {
@@ -110,6 +133,14 @@ const writeServices = async (inputs: Inputs): Promise<void> => {
110133
)
111134

112135
const namespaceApplicationManifestPath = `${inputs.namespaceDirectory}/applications/${inputs.namespace}--${service}.yaml`
136+
137+
if (shouldServiceExcluded(service, inputs.excludeServices, inputs.invertExcludeServices)) {
138+
core.info(
139+
`Preserving the existing application manifest: ${namespaceApplicationManifestPath} because the service is excluded`,
140+
)
141+
continue
142+
}
143+
113144
if (existingApplicationManifestPaths.includes(namespaceApplicationManifestPath)) {
114145
core.info(`Preserving the existing application manifest: ${namespaceApplicationManifestPath}`)
115146
continue

bootstrap-pull-request/src/run.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ type Inputs = {
1515
namespaceManifest: string | undefined
1616
substituteVariables: string[]
1717
currentHeadSha: string
18+
excludeServices: string[]
19+
invertExcludeServices: boolean
1820
}
1921

2022
type Outputs = {
@@ -54,6 +56,8 @@ const bootstrapNamespace = async (inputs: Inputs): Promise<Outputs | Error> => {
5456
prebuiltDirectory,
5557
namespaceDirectory,
5658
substituteVariables,
59+
excludeServices: inputs.excludeServices,
60+
invertExcludeServices: inputs.invertExcludeServices,
5761
})
5862

5963
if (inputs.namespaceManifest) {

bootstrap-pull-request/tests/prebuilt.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ describe('syncServicesFromPrebuilt', () => {
2121
prebuiltDirectory: `${__dirname}/fixtures/prebuilt`,
2222
namespaceDirectory,
2323
substituteVariables: new Map<string, string>([['NAMESPACE', 'pr-123']]),
24+
excludeServices: [],
25+
invertExcludeServices: false,
2426
})
2527

2628
expect(services).toStrictEqual<Service[]>([
@@ -67,6 +69,8 @@ describe('syncServicesFromPrebuilt', () => {
6769
prebuiltDirectory: `${__dirname}/fixtures/prebuilt`,
6870
namespaceDirectory,
6971
substituteVariables: new Map<string, string>([['NAMESPACE', 'pr-123']]),
72+
excludeServices: [],
73+
invertExcludeServices: false,
7074
})
7175

7276
expect(services).toStrictEqual<Service[]>([
@@ -113,6 +117,8 @@ describe('syncServicesFromPrebuilt', () => {
113117
prebuiltDirectory: `${__dirname}/fixtures/prebuilt`,
114118
namespaceDirectory,
115119
substituteVariables: new Map<string, string>([['NAMESPACE', 'pr-123']]),
120+
excludeServices: [],
121+
invertExcludeServices: false,
116122
})
117123

118124
expect(services).toStrictEqual<Service[]>([
@@ -136,6 +142,60 @@ describe('syncServicesFromPrebuilt', () => {
136142
expect(await readContent(`${namespaceDirectory}/services/b/generated.yaml`)).toBe(serviceB)
137143
})
138144

145+
it('does not write service manifest if it was exluded', async () => {
146+
const namespaceDirectory = await createEmptyDirectory()
147+
await fs.mkdir(`${namespaceDirectory}/applications`)
148+
await fs.mkdir(`${namespaceDirectory}/services/a`, { recursive: true })
149+
await fs.writeFile(`${namespaceDirectory}/applications/pr-123--a.yaml`, applicationPushedOnOutdatedCommit)
150+
await fs.writeFile(`${namespaceDirectory}/services/a/generated.yaml`, 'this-should-be-kept')
151+
152+
await syncServicesFromPrebuilt({
153+
currentHeadSha: 'current-sha',
154+
overlay: 'pr',
155+
namespace: 'pr-123',
156+
sourceRepositoryName: 'source-repository',
157+
destinationRepository: 'octocat/destination-repository',
158+
prebuiltBranch: 'prebuilt/source-repository/pr',
159+
prebuiltDirectory: `${__dirname}/fixtures/prebuilt`,
160+
namespaceDirectory,
161+
substituteVariables: new Map<string, string>([['NAMESPACE', 'pr-123']]),
162+
excludeServices: ['a'],
163+
invertExcludeServices: false,
164+
})
165+
166+
expect(await fs.readdir(`${namespaceDirectory}/applications`)).toStrictEqual(['pr-123--a.yaml', 'pr-123--b.yaml'])
167+
expect(await readContent(`${namespaceDirectory}/applications/pr-123--a.yaml`)).toBe(
168+
applicationPushedOnOutdatedCommit,
169+
)
170+
expect(await readContent(`${namespaceDirectory}/services/a/generated.yaml`)).toBe('this-should-be-kept')
171+
})
172+
173+
it('writes service manifest if it was exluded but inverted', async () => {
174+
const namespaceDirectory = await createEmptyDirectory()
175+
await fs.mkdir(`${namespaceDirectory}/applications`)
176+
await fs.mkdir(`${namespaceDirectory}/services/a`, { recursive: true })
177+
await fs.writeFile(`${namespaceDirectory}/applications/pr-123--a.yaml`, applicationPushedOnOutdatedCommit)
178+
await fs.writeFile(`${namespaceDirectory}/services/a/generated.yaml`, 'this-should-be-updated')
179+
180+
await syncServicesFromPrebuilt({
181+
currentHeadSha: 'current-sha',
182+
overlay: 'pr',
183+
namespace: 'pr-123',
184+
sourceRepositoryName: 'source-repository',
185+
destinationRepository: 'octocat/destination-repository',
186+
prebuiltBranch: 'prebuilt/source-repository/pr',
187+
prebuiltDirectory: `${__dirname}/fixtures/prebuilt`,
188+
namespaceDirectory,
189+
substituteVariables: new Map<string, string>([['NAMESPACE', 'pr-123']]),
190+
excludeServices: ['a'],
191+
invertExcludeServices: true,
192+
})
193+
194+
expect(await fs.readdir(`${namespaceDirectory}/applications`)).toStrictEqual(['pr-123--a.yaml'])
195+
expect(await readContent(`${namespaceDirectory}/applications/pr-123--a.yaml`)).toBe(applicationA)
196+
expect(await readContent(`${namespaceDirectory}/services/a/generated.yaml`)).toBe(serviceA)
197+
})
198+
139199
it('deletes a service which does not exist in the prebuilt branch', async () => {
140200
const namespaceDirectory = await createEmptyDirectory()
141201
await fs.mkdir(`${namespaceDirectory}/applications`)
@@ -151,6 +211,8 @@ describe('syncServicesFromPrebuilt', () => {
151211
prebuiltDirectory: `${__dirname}/fixtures/prebuilt`,
152212
namespaceDirectory,
153213
substituteVariables: new Map<string, string>([['NAMESPACE', 'pr-123']]),
214+
excludeServices: [],
215+
invertExcludeServices: false,
154216
})
155217

156218
expect(services).toStrictEqual<Service[]>([
@@ -199,6 +261,8 @@ describe('syncServicesFromPrebuilt', () => {
199261
prebuiltDirectory: `${__dirname}/fixtures/prebuilt`,
200262
namespaceDirectory,
201263
substituteVariables: new Map<string, string>([['NAMESPACE', 'pr-123']]),
264+
excludeServices: [],
265+
invertExcludeServices: false,
202266
})
203267

204268
expect(services).toStrictEqual<Service[]>([

get-service-versions/README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# get-service-versions [![get-service-versions](https://github.com/quipper/monorepo-deploy-actions/actions/workflows/get-service-versions.yaml/badge.svg)](https://github.com/quipper/monorepo-deploy-actions/actions/workflows/get-service-versions.yaml)
2+
3+
This is an action to get service versions (commit hash) pushed by `git-push-service` action.
4+
5+
## Getting Started
6+
7+
```yaml
8+
name: pr-namespace / get-service-versions
9+
10+
on:
11+
pull_request:
12+
13+
jobs:
14+
get-service-versions:
15+
runs-on: ubuntu-latest
16+
timeout-minutes: 10
17+
steps:
18+
- uses: quipper/monorepo-deploy-actions/get-service-versions@v1
19+
with:
20+
namespace: pr-${{ github.event.number }}
21+
repository: octocat/generated-manifests
22+
repository-token: ${{ steps.destination-repository-github-app.outputs.token }}
23+
```
24+
25+
It assumes that the below name of prebuilt branch exists in the destination repository.
26+
27+
```
28+
prebuilt/${source-repository}/${overlay}
29+
```
30+
31+
## Specification
32+
33+
See [action.yaml](action.yaml).

get-service-versions/action.yaml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: get-service-versions
2+
description: get the pushed service versions
3+
4+
inputs:
5+
overlay:
6+
description: Name of overlay
7+
required: true
8+
namespace:
9+
description: Name of namespace
10+
required: true
11+
source-repository:
12+
description: Source repository
13+
required: true
14+
default: ${{ github.repository }}
15+
destination-repository:
16+
description: Destination repository
17+
required: true
18+
destination-repository-token:
19+
description: GitHub token for destination repository
20+
required: true
21+
default: ${{ github.token }}
22+
23+
outputs:
24+
application-versions:
25+
description: 'JSON array of object containing keys: service, action, headRef, headSha'
26+
27+
runs:
28+
using: 'node20'
29+
main: 'dist/index.js'
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/** @type {import('ts-jest').JestConfigWithTsJest} */
2+
export default {
3+
preset: 'ts-jest/presets/default-esm',
4+
clearMocks: true,
5+
// https://kulshekhar.github.io/ts-jest/docs/guides/esm-support/
6+
moduleNameMapper: {
7+
'^(\\.{1,2}/.*)\\.js$': '$1',
8+
},
9+
}

get-service-versions/package.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "get-service-versions",
3+
"version": "0.0.0",
4+
"private": true,
5+
"type": "module",
6+
"scripts": {
7+
"build": "ncc build --source-map --license licenses.txt src/main.ts",
8+
"test": "jest"
9+
},
10+
"dependencies": {
11+
"@actions/core": "1.11.1",
12+
"@actions/exec": "1.1.1",
13+
"@actions/glob": "0.5.0",
14+
"js-yaml": "4.1.0"
15+
},
16+
"devDependencies": {
17+
"@types/js-yaml": "4.0.9"
18+
}
19+
}

0 commit comments

Comments
 (0)