Skip to content

Commit d3edd84

Browse files
committed
generate backward incompatible report
1 parent 011902a commit d3edd84

10 files changed

Lines changed: 770 additions & 187 deletions
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
name: Backward Compatible Report
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
opensearch_version:
7+
description: 'OpenSearch version. Leave empty to fetch latest.'
8+
required: false
9+
type: string
10+
spec_artifact_name:
11+
description: 'Name of the uploaded artifact containing opensearch-openapi.yaml'
12+
required: false
13+
type: string
14+
default: 'openapi-spec'
15+
outputs:
16+
report:
17+
description: 'The compatibility report in markdown format'
18+
value: ${{ jobs.generate-report.outputs.report }}
19+
20+
workflow_dispatch:
21+
inputs:
22+
opensearch_version:
23+
description: 'OpenSearch version. Leave empty to fetch latest.'
24+
required: false
25+
type: string
26+
27+
jobs:
28+
generate-report:
29+
runs-on: ubuntu-latest
30+
outputs:
31+
report: ${{ steps.merge_report.outputs.report }}
32+
steps:
33+
- name: Checkout Repository
34+
uses: actions/checkout@v4
35+
with:
36+
repository: ${{ github.repository_owner }}/opensearch-protobufs
37+
ref: main
38+
39+
- name: Setup Node.js
40+
uses: actions/setup-node@v3
41+
with:
42+
node-version: 20
43+
44+
- name: Setup Java
45+
uses: actions/setup-java@v3
46+
with:
47+
distribution: temurin
48+
java-version: 17
49+
50+
- name: Download OpenAPI spec artifact
51+
if: ${{ inputs.spec_artifact_name != '' }}
52+
uses: actions/download-artifact@v4
53+
with:
54+
name: ${{ inputs.spec_artifact_name }}
55+
path: .
56+
57+
- name: Get Latest OpenSearch Core Version
58+
id: get_opensearch_version
59+
uses: actions/github-script@v6
60+
with:
61+
github-token: ${{ secrets.GITHUB_TOKEN }}
62+
script: |
63+
// Check if version was provided as input
64+
const inputVersion = "${{ inputs.opensearch_version }}";
65+
if (inputVersion && inputVersion.trim() !== "") {
66+
core.setOutput("version", inputVersion.trim());
67+
console.log("Using provided OpenSearch version: " + inputVersion.trim());
68+
return;
69+
}
70+
71+
// Otherwise fetch version from buildSrc/version.properties on main branch
72+
try {
73+
const response = await github.request(
74+
'GET /repos/{owner}/{repo}/contents/{path}',
75+
{
76+
owner: 'opensearch-project',
77+
repo: 'OpenSearch',
78+
path: 'buildSrc/version.properties',
79+
ref: 'main'
80+
}
81+
);
82+
83+
// Decode the file content
84+
const content = Buffer.from(response.data.content, 'base64').toString('utf-8');
85+
86+
// Extract opensearch version (e.g., "opensearch = 3.4.0")
87+
const match = content.match(/opensearch\s*=\s*([^\s]+)/);
88+
89+
if (match && match[1]) {
90+
const version = match[1].trim();
91+
core.setOutput("version", version);
92+
console.log("Fetched OpenSearch version from source: " + version);
93+
} else {
94+
core.setFailed("Could not find opensearch version in version.properties");
95+
}
96+
} catch (error) {
97+
core.setFailed("Could not fetch OpenSearch version: " + error.message);
98+
}
99+
100+
- name: Run Proto Conversion
101+
env:
102+
OPENSEARCH_VERSION: ${{ steps.get_opensearch_version.outputs.version }}
103+
run: npm ci && npm run preprocessing -- --opensearch-version "$OPENSEARCH_VERSION"
104+
105+
- name: Clone Protobuf Generator Repository
106+
run: |
107+
git clone https://github.com/OpenAPITools/openapi-generator cloned-repo
108+
cd cloned-repo
109+
git checkout 6699ecd9d2f4e0868f23bb36566ea03cd1230e6a
110+
111+
- name: Build Protobuf Generator Tool
112+
run: |
113+
cd cloned-repo
114+
./mvnw clean package
115+
116+
- name: Convert protobuf
117+
run: |
118+
java -jar cloned-repo/modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -c tools/proto-convert/src/config/protobuf-generator-config.yaml
119+
120+
- name: Post Process Protobuf (dry-run for report)
121+
id: merge_report
122+
run: |
123+
npm run postprocessing:dry-run
124+
REPORT_PATH="/tmp/merge-report.md"
125+
126+
echo "=== Compatibility Report ==="
127+
cat "$REPORT_PATH"
128+
echo "============================="
129+
130+
REPORT=$(cat "$REPORT_PATH")
131+
echo "report<<EOF" >> $GITHUB_OUTPUT
132+
echo "$REPORT" >> $GITHUB_OUTPUT
133+
echo "EOF" >> $GITHUB_OUTPUT

.github/workflows/convert-proto.yml

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ on:
77
description: 'OpenSearch version. Leave empty to fetch latest.'
88
required: false
99
type: string
10+
repository_dispatch:
11+
types: [spec-updated]
12+
1013
jobs:
1114
auto-proto-convert:
1215
runs-on: ubuntu-latest
13-
if: github.repository == 'opensearch-project/opensearch-protobufs'
16+
# if: github.repository == 'opensearch-project/opensearch-protobufs'
1417
steps:
1518
- name: Checkout Repository
1619
uses: actions/checkout@v4
@@ -26,15 +29,10 @@ jobs:
2629
distribution: temurin
2730
java-version: 17
2831

29-
- name: Install buf
30-
run: |
31-
npm install -g @bufbuild/buf
32-
buf --version
33-
3432
- name: Download Release Assets
3533
uses: robinraju/release-downloader@v1
3634
with:
37-
repository: 'opensearch-project/opensearch-api-specification'
35+
repository: 'lucy66hw/opensearch-api-specification'
3836
latest: true
3937
fileName: 'opensearch-openapi.yaml'
4038
tag: 'main-latest'
@@ -121,12 +119,19 @@ jobs:
121119
run: |
122120
java -jar cloned-repo/modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -c tools/proto-convert/src/config/protobuf-generator-config.yaml
123121
124-
- name: Reformat proto files
125-
run: |
126-
buf format -w protos/generated
127-
128122
- name: Post Process Protobuf
129-
run: npm run postprocessing
123+
id: merge_report
124+
run: |
125+
npm run postprocessing
126+
REPORT_PATH="/tmp/merge-report.md"
127+
if [ -f "$REPORT_PATH" ]; then
128+
REPORT=$(cat "$REPORT_PATH")
129+
echo "report<<EOF" >> $GITHUB_OUTPUT
130+
echo "$REPORT" >> $GITHUB_OUTPUT
131+
echo "EOF" >> $GITHUB_OUTPUT
132+
else
133+
echo "report=No changes detected." >> $GITHUB_OUTPUT
134+
fi
130135
131136
- name: Configure Git User
132137
run: |
@@ -161,3 +166,7 @@ jobs:
161166
162167
**OpenSearch Version**: ${{ steps.get_opensearch_version.outputs.version }}
163168
**API Spec Commit**: ${{ steps.get_commit.outputs.latest_commit }}
169+
170+
---
171+
172+
${{ steps.merge_report.outputs.report }}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"backward-compat": "ts-node tools/proto-convert/src/postprocessing/BackwardCompatibleWriter.ts",
1010
"cleanup-unused": "ts-node tools/proto-convert/src/postprocessing/CleanupUnusedMessages.ts -i protos/schemas/common.proto",
1111
"postprocessing": "npm run backward-compat && npm run cleanup-unused",
12+
"postprocessing:dry-run": "npm run backward-compat -- --dry-run",
1213
"test": "npx jest --no-watchman"
1314
},
1415
"dependencies": {

tools/proto-convert/src/postprocessing/BackwardCompatibleWriter.ts

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
1-
import { existsSync } from 'fs';
1+
import { existsSync, writeFileSync } from 'fs';
2+
import { tmpdir } from 'os';
3+
import { join } from 'path';
24
import { Command, Option } from '@commander-js/extra-typings';
3-
import {
4-
ProtoMessage,
5-
ProtoEnum,
6-
BackwardCompatibilityError
7-
} from './types';
5+
import { ProtoMessage, ProtoEnum } from './types';
86
import { parseProtoFile } from './parser';
97
import { mergeMessage, mergeEnum } from './CompatibilityMerger';
108
import { writeProtoFile, CUSTOM_MESSAGE_NAMES, CUSTOM_ENUM_NAMES } from './writer';
9+
import { CompatibilityReporter } from './CompatibilityReporter';
1110
import logger from '../utils/logger';
1211

1312
export class BackwardCompatibleWriter {
1413
private existingMessages: ProtoMessage[];
1514
private existingEnums: ProtoEnum[];
1615
private incomingMessageMap: Map<string, ProtoMessage> = new Map();
1716
private incomingEnumMap: Map<string, ProtoEnum> = new Map();
18-
private errors: string[] = [];
1917
private outputPath: string;
18+
private reporter: CompatibilityReporter = new CompatibilityReporter();
2019

2120
constructor(existingPath: string, incomingPaths: string[], outputPath: string) {
2221
this.outputPath = outputPath;
@@ -46,7 +45,7 @@ export class BackwardCompatibleWriter {
4645
}
4746
}
4847

49-
process(): void {
48+
process(dryRun: boolean = false): void {
5049
const finalMessages: ProtoMessage[] = [];
5150
const finalEnums: ProtoEnum[] = [];
5251

@@ -59,7 +58,7 @@ export class BackwardCompatibleWriter {
5958

6059
const incomingMsg = this.incomingMessageMap.get(existingMsg.name);
6160
if (incomingMsg) {
62-
finalMessages.push(mergeMessage(existingMsg, incomingMsg, this.errors));
61+
finalMessages.push(mergeMessage(existingMsg, incomingMsg, this.reporter));
6362
this.incomingMessageMap.delete(existingMsg.name);
6463
} else {
6564
finalMessages.push(existingMsg);
@@ -75,7 +74,7 @@ export class BackwardCompatibleWriter {
7574

7675
const incomingEnum = this.incomingEnumMap.get(existingEnum.name);
7776
if (incomingEnum) {
78-
finalEnums.push(mergeEnum(existingEnum, incomingEnum));
77+
finalEnums.push(mergeEnum(existingEnum, incomingEnum, this.reporter));
7978
this.incomingEnumMap.delete(existingEnum.name);
8079
} else {
8180
finalEnums.push(existingEnum);
@@ -96,20 +95,20 @@ export class BackwardCompatibleWriter {
9695
}
9796
}
9897

99-
// Check for errors before writing
100-
if (this.errors.length > 0) {
101-
logger.error('Backward compatibility errors:');
102-
for (const error of this.errors) {
103-
logger.error(` ${error}`);
104-
}
105-
throw new BackwardCompatibilityError(
106-
`Found ${this.errors.length} backward compatibility violation(s).`
107-
);
98+
// Write output using shared function (skip if dry-run)
99+
if (dryRun) {
100+
logger.info(`Dry run: would update ${this.outputPath}`);
101+
} else {
102+
writeProtoFile(finalMessages, finalEnums, this.outputPath);
103+
logger.info(`Updated: ${this.outputPath}`);
108104
}
105+
}
109106

110-
// Write output using shared function
111-
writeProtoFile(finalMessages, finalEnums, this.outputPath);
112-
logger.info(`Updated: ${this.outputPath}`);
107+
/**
108+
* Get the merge reporter for accessing change reports.
109+
*/
110+
getReporter(): CompatibilityReporter {
111+
return this.reporter;
113112
}
114113
}
115114

@@ -124,13 +123,15 @@ if (require.main === module) {
124123
.argParser((val: string) => val.split(',').map(s => s.trim()))
125124
.default(['protos/generated/models/aggregated_models.proto', 'protos/generated/services/default_service.proto']))
126125
.addOption(new Option('-o, --output <path>', 'output proto file').default('protos/schemas/common.proto'))
126+
.addOption(new Option('-d, --dry-run', 'preview changes without writing output file').default(false))
127127
.allowExcessArguments(false)
128128
.parse();
129129

130130
type BackwardCompatOpts = {
131131
existing: string;
132132
incoming: string[];
133133
output: string;
134+
dryRun: boolean;
134135
};
135136

136137
const opts = command.opts() as BackwardCompatOpts;
@@ -146,17 +147,17 @@ if (require.main === module) {
146147
process.exit(1);
147148
}
148149

149-
try {
150-
const writer = new BackwardCompatibleWriter(
151-
opts.existing,
152-
opts.incoming,
153-
opts.output
154-
);
155-
writer.process();
156-
} catch (error) {
157-
if (error instanceof BackwardCompatibilityError) {
158-
process.exit(1);
159-
}
160-
throw error;
161-
}
150+
const writer = new BackwardCompatibleWriter(
151+
opts.existing,
152+
opts.incoming,
153+
opts.output
154+
);
155+
156+
// Process and merge
157+
writer.process(opts.dryRun);
158+
159+
// write report to temp directory
160+
const reportPath = join(tmpdir(), 'merge-report.md');
161+
writeFileSync(reportPath, writer.getReporter().toMarkdown());
162+
logger.info(`Report written: ${reportPath}`);
162163
}

0 commit comments

Comments
 (0)