Skip to content

Commit fbdc8a0

Browse files
authored
GitHub Action to prepare a branch for the next release. (opensearch-project#6487)
Signed-off-by: David Venable <dlv@amazon.com>
1 parent 7d5e8de commit fbdc8a0

10 files changed

Lines changed: 534 additions & 0 deletions

File tree

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
#
2+
# Copyright OpenSearch Contributors
3+
# SPDX-License-Identifier: Apache-2.0
4+
#
5+
# The OpenSearch Contributors require contributions made to
6+
# this file be licensed under the Apache-2.0 license or a
7+
# compatible open source license.
8+
#
9+
10+
# This workflow automates the preparation of patch release branches.
11+
# It validates the release branch, updates gradle.properties to the next version
12+
# generates the THIRD-PARTY file, and creates a pull request with all changes.
13+
14+
name: Prepare Release Branch
15+
16+
on:
17+
workflow_dispatch:
18+
19+
permissions:
20+
contents: write
21+
pull-requests: write
22+
23+
jobs:
24+
prepare-release:
25+
runs-on: ubuntu-latest
26+
27+
steps:
28+
- name: Checkout Data Prepper
29+
uses: actions/checkout@v2
30+
31+
- name: Validate release branch
32+
id: validate_branch
33+
run: |
34+
BRANCH_NAME=${GITHUB_REF#refs/heads/}
35+
echo "Running on branch: $BRANCH_NAME"
36+
37+
# Validate branch name matches {major}.{minor} pattern
38+
if ! [[ $BRANCH_NAME =~ ^[0-9]+\.[0-9]+$ ]]; then
39+
echo "::error::Invalid release branch name: $BRANCH_NAME"
40+
echo "This workflow must run on a release branch with format {major}.{minor} (e.g., '2.6')"
41+
echo "Current branch: $BRANCH_NAME"
42+
echo ""
43+
echo "To run this workflow:"
44+
echo " 1. Switch to a release branch (e.g., 2.6)"
45+
echo " 2. Navigate to Actions > Prepare Patch Release"
46+
echo " 3. Click 'Run workflow' and select the release branch"
47+
exit 1
48+
fi
49+
50+
echo "✅ Valid release branch: $BRANCH_NAME"
51+
echo "branch=$BRANCH_NAME" >> $GITHUB_OUTPUT
52+
53+
- name: Set up JDK
54+
uses: actions/setup-java@v4
55+
with:
56+
java-version: 21
57+
distribution: temurin
58+
59+
- name: Update version to next release
60+
id: version
61+
run: |
62+
./gradlew updateToNextVersion
63+
64+
RELEASE_VERSION=$(./gradlew -q printCurrentVersion)
65+
echo "version=$RELEASE_VERSION" >> $GITHUB_OUTPUT
66+
67+
- name: Generate THIRD-PARTY file
68+
run: |
69+
echo "Generating THIRD-PARTY file..."
70+
71+
# Run the Gradle task to generate the THIRD-PARTY file
72+
# Capture both stdout and stderr for better error reporting
73+
if ! OUTPUT=$(./gradlew --no-daemon generateThirdPartyReport 2>&1); then
74+
echo "::error::Failed to generate THIRD-PARTY file"
75+
echo "Gradle output:"
76+
echo "$OUTPUT"
77+
exit 1
78+
fi
79+
80+
# Verify the THIRD-PARTY file was created/updated
81+
if [ ! -f THIRD-PARTY ]; then
82+
echo "::error::THIRD-PARTY file not found after generation"
83+
echo "The generateThirdPartyReport task completed but did not create the expected file"
84+
exit 1
85+
fi
86+
87+
echo "Successfully generated THIRD-PARTY file"
88+
89+
- name: GitHub App token
90+
id: github_app_token
91+
uses: tibdex/github-app-token@v2
92+
with:
93+
app_id: ${{ secrets.APP_ID }}
94+
private_key: ${{ secrets.APP_PRIVATE_KEY }}
95+
96+
- name: Create Pull Request
97+
id: create_pr
98+
uses: peter-evans/create-pull-request@v6
99+
with:
100+
token: ${{ steps.github_app_token.outputs.token }}
101+
add-paths: |
102+
gradle.properties
103+
THIRD-PARTY
104+
commit-message: 'Prepare release ${{ steps.version.outputs.version }}'
105+
signoff: true
106+
branch: prepare-release-${{ steps.version.outputs.version }}
107+
delete-branch: true
108+
base: ${{ steps.validate_branch.outputs.branch }}
109+
title: 'Prepare release ${{ steps.version.outputs.version }}'
110+
body: |
111+
## Automated Release Preparation
112+
113+
This PR prepares the release branch for version **${{ steps.version.outputs.version }}**.
114+
115+
### Automated Changes
116+
- Updated `gradle.properties` version to `${{ steps.version.outputs.version }}`
117+
- Generated `THIRD-PARTY` file
118+
119+
### Manual Steps Remaining
120+
121+
Before merging this PR:
122+
- [ ] Review the version number is correct
123+
- [ ] Review the THIRD-PARTY file changes
124+
125+
After merging this PR:
126+
- [ ] Prepare release notes (see [release notes script](release/script/release-notes/README.md))
127+
- [ ] Run the [Release Artifacts workflow](https://github.com/opensearch-project/data-prepper/actions/workflows/release.yml)
128+
- [ ] Approve the release issue (requires 2 maintainer approvals)
129+
- [ ] Update the draft release with release notes
130+
- [ ] Publish the release
131+
- [ ] Close the milestone
132+
133+
---
134+
135+
Branch: ${{ steps.validate_branch.outputs.branch }}
136+
137+
Generated by the [Prepare Patch Release workflow](https://github.com/opensearch-project/data-prepper/actions/workflows/release-prepare-patch.yml)
138+
139+
- name: Output PR URL
140+
if: steps.create_pr.outputs.pull-request-url
141+
run: |
142+
echo "✅ Pull Request created successfully!"
143+
echo "URL: ${{ steps.create_pr.outputs.pull-request-url }}"
144+
echo "::notice::Pull Request created: ${{ steps.create_pr.outputs.pull-request-url }}"
145+
146+
- name: Check PR creation
147+
if: steps.create_pr.outcome == 'failure'
148+
run: |
149+
echo "::error::Failed to create pull request"
150+
echo "This could be due to:"
151+
echo " - Missing or invalid GitHub App credentials"
152+
echo " - Insufficient permissions"
153+
echo " - Network issues"
154+
echo "Please check the logs above for more details"
155+
exit 1
156+
157+
- name: Workflow summary
158+
if: always()
159+
run: |
160+
echo "## Workflow Execution Summary" >> $GITHUB_STEP_SUMMARY
161+
echo "" >> $GITHUB_STEP_SUMMARY
162+
163+
if [ "${{ steps.validate_branch.outcome }}" == "success" ]; then
164+
echo "✅ Branch validation: **${{ steps.validate_branch.outputs.branch }}**" >> $GITHUB_STEP_SUMMARY
165+
else
166+
echo "❌ Branch validation failed" >> $GITHUB_STEP_SUMMARY
167+
fi
168+
169+
if [ "${{ steps.version.outcome }}" == "success" ]; then
170+
echo "✅ Version determination: **${{ steps.version.outputs.version }}**" >> $GITHUB_STEP_SUMMARY
171+
else
172+
echo "❌ Version determination failed" >> $GITHUB_STEP_SUMMARY
173+
fi
174+
175+
if [ "${{ steps.create_pr.outcome }}" == "success" ]; then
176+
echo "✅ Pull request created: ${{ steps.create_pr.outputs.pull-request-url }}" >> $GITHUB_STEP_SUMMARY
177+
else
178+
echo "❌ Pull request creation failed" >> $GITHUB_STEP_SUMMARY
179+
fi

build.gradle

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
/*
22
* Copyright OpenSearch Contributors
33
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* The OpenSearch Contributors require contributions made to
6+
* this file be licensed under the Apache-2.0 license or a
7+
* compatible open source license.
48
*/
59
import com.github.jk1.license.render.TextReportRenderer
10+
import org.opensearch.dataprepper.gradle.release.PrintCurrentVersionTask
11+
import org.opensearch.dataprepper.gradle.release.PrintNextVersionTask
12+
import org.opensearch.dataprepper.gradle.release.UpdateVersionTask
613

714
buildscript {
815
dependencies {
@@ -328,6 +335,21 @@ task generateThirdPartyReport(type: Copy) {
328335
generateThirdPartyReport.dependsOn(generateLicenseReport)
329336
}
330337

338+
tasks.register('printNextVersion', PrintNextVersionTask) {
339+
description = 'Prints the next release version based on the current version in gradle.properties'
340+
group = 'release'
341+
}
342+
343+
tasks.register('printCurrentVersion', PrintCurrentVersionTask) {
344+
description = 'Prints the current version from gradle.properties'
345+
group = 'release'
346+
}
347+
348+
tasks.register('updateToNextVersion', UpdateVersionTask) {
349+
description = 'Updates gradle.properties to the next release version'
350+
group = 'release'
351+
}
352+
331353
// Utility method to recursively collect tasks from all projects and subprojects
332354
def collectTasksRecursively(Project project, String taskName) {
333355
def tasks = []

buildSrc/build.gradle

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,10 @@ repositories {
1515

1616
dependencies {
1717
implementation('me.champeau.jmh:me.champeau.jmh.gradle.plugin:0.7.2')
18+
testImplementation('org.junit.jupiter:junit-jupiter:5.9.3')
19+
testImplementation('org.hamcrest:hamcrest:2.2')
20+
}
21+
22+
test {
23+
useJUnitPlatform()
1824
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* The OpenSearch Contributors require contributions made to
6+
* this file be licensed under the Apache-2.0 license or a
7+
* compatible open source license.
8+
*/
9+
10+
package org.opensearch.dataprepper.gradle.release;
11+
12+
import org.gradle.api.DefaultTask;
13+
import org.gradle.api.tasks.TaskAction;
14+
15+
/**
16+
* Gradle task that prints the current version from gradle.properties.
17+
*/
18+
public class PrintCurrentVersionTask extends DefaultTask {
19+
20+
@TaskAction
21+
public void printCurrentVersion() {
22+
System.out.println(getProject().getVersion().toString());
23+
}
24+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* The OpenSearch Contributors require contributions made to
6+
* this file be licensed under the Apache-2.0 license or a
7+
* compatible open source license.
8+
*/
9+
10+
package org.opensearch.dataprepper.gradle.release;
11+
12+
import org.gradle.api.DefaultTask;
13+
import org.gradle.api.tasks.TaskAction;
14+
15+
/**
16+
* Gradle task that prints the next release version based on the current version in gradle.properties.
17+
*/
18+
public class PrintNextVersionTask extends DefaultTask {
19+
20+
/**
21+
* Executes the task to determine and print the next version.
22+
*/
23+
@TaskAction
24+
public void printNextVersion() {
25+
final String currentVersion = getProject().getVersion().toString();
26+
final String nextVersion = VersionCalculator.determineNextVersion(currentVersion);
27+
System.out.println(nextVersion);
28+
}
29+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* The OpenSearch Contributors require contributions made to
6+
* this file be licensed under the Apache-2.0 license or a
7+
* compatible open source license.
8+
*/
9+
10+
package org.opensearch.dataprepper.gradle.release;
11+
12+
import org.gradle.api.DefaultTask;
13+
import org.gradle.api.GradleException;
14+
import org.gradle.api.tasks.TaskAction;
15+
16+
import java.io.IOException;
17+
import java.nio.file.Path;
18+
19+
/**
20+
* Gradle task that updates the version in gradle.properties to the next release version.
21+
*/
22+
public class UpdateVersionTask extends DefaultTask {
23+
24+
@TaskAction
25+
public void updateVersion() {
26+
final String currentVersion = getProject().getVersion().toString();
27+
final String nextVersion = VersionCalculator.determineNextVersion(currentVersion);
28+
final Path propertiesFile = getProject().getRootDir().toPath().resolve("gradle.properties");
29+
30+
try {
31+
VersionUpdater.updateVersionInFile(propertiesFile, nextVersion);
32+
getLogger().lifecycle("Updated version from {} to {}", currentVersion, nextVersion);
33+
} catch (IOException e) {
34+
throw new GradleException("Failed to update gradle.properties", e);
35+
}
36+
}
37+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* The OpenSearch Contributors require contributions made to
6+
* this file be licensed under the Apache-2.0 license or a
7+
* compatible open source license.
8+
*/
9+
10+
package org.opensearch.dataprepper.gradle.release;
11+
12+
import java.util.regex.Matcher;
13+
import java.util.regex.Pattern;
14+
15+
/**
16+
* Utility class for calculating the next release version from a current version string.
17+
*/
18+
class VersionCalculator {
19+
private static final Pattern VERSION_PATTERN = Pattern.compile("^(\\d+)\\.(\\d+)\\.(\\d+)(-SNAPSHOT)?$");
20+
21+
/**
22+
* Determines the next release version from the current version.
23+
* <p>
24+
* If the current version ends with "-SNAPSHOT", the suffix is removed.
25+
* If the current version does not end with "-SNAPSHOT", the patch version is incremented by 1.
26+
*
27+
* @param currentVersion The current version string (e.g., "2.6.2-SNAPSHOT" or "2.6.2")
28+
* @return The next release version (e.g., "2.6.2" or "2.6.3")
29+
* @throws IllegalArgumentException if version format is invalid
30+
* @throws NullPointerException if currentVersion is null
31+
*/
32+
static String determineNextVersion(String currentVersion) {
33+
if (currentVersion == null) {
34+
throw new NullPointerException("currentVersion cannot be null");
35+
}
36+
37+
Matcher matcher = VERSION_PATTERN.matcher(currentVersion);
38+
if (!matcher.matches()) {
39+
throw new IllegalArgumentException(
40+
"Invalid version format: " + currentVersion +
41+
". Expected format: {major}.{minor}.{patch}[-SNAPSHOT]"
42+
);
43+
}
44+
45+
String major = matcher.group(1);
46+
String minor = matcher.group(2);
47+
String patch = matcher.group(3);
48+
String snapshot = matcher.group(4);
49+
50+
// If version has -SNAPSHOT suffix, remove it
51+
if (snapshot != null) {
52+
return major + "." + minor + "." + patch;
53+
}
54+
55+
// Otherwise, increment patch version
56+
int patchNumber = Integer.parseInt(patch);
57+
int nextPatch = patchNumber + 1;
58+
return major + "." + minor + "." + nextPatch;
59+
}
60+
}

0 commit comments

Comments
 (0)