Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions .ci/ReleaseChangelog.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.OptionalInt;
import java.util.function.Predicate;

public class ReleaseChangelog {

public static void main(String[] args) throws IOException {
if (args.length != 2) {
System.out.println("Expected exactly two arguments: <ChangelogFile> <VersionToRelease>");
System.exit(-1);
}
Path fileName = Paths.get(args[0]);
String version = args[1].trim();
if (!version.matches("\\d+\\.\\d+\\.\\d+")) {
System.out.println("Version must be in the format x.x.x but was not: " + version);
System.exit(-1);
}
Lines f = new Lines(Files.readAllLines(fileName, StandardCharsets.UTF_8));
int unreleasedStart = f.findLine(str -> str.trim().equals("=== Unreleased"), 0).orElseThrow() + 1;
int unreleasedEnd = f.findLine(str -> str.startsWith("[[release-notes-"), unreleasedStart).orElseThrow();

Lines changes = f.cut(unreleasedStart, unreleasedEnd);
f.insert(new Lines(List.of("")), unreleasedStart); //add a blank line below unreleased heading
changes.trim();

// a few sanity checks
if (changes.lineCount() == 0) {
System.out.println("Unreleased changes are empty, there must be something wrong!");
System.exit(-1);
}
OptionalInt wrongIndentedHeading = changes.findLine(str -> str.matches("^==?=?=?[^=].*"), 0);
if (wrongIndentedHeading.isPresent()) {
System.out.println("Found heading which is too high level (must be at least =====) within changes: "
+ changes.getLine(wrongIndentedHeading.getAsInt()));
System.exit(-1);
}

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd")
.withZone(ZoneOffset.UTC);

changes.insert(new Lines(List.of(
"",
String.format("[[release-notes-%s]]", version),
String.format("==== %s - %s", version, formatter.format(Instant.now())),
""
)), 0);

int majorVersion = Integer.parseInt(version.split("\\.")[0]);
String majorHeading = String.format("=== Java Agent version %d.x", majorVersion);
OptionalInt sectionStart = f.findLine(str -> str.trim().equals(majorHeading), 0);
if (sectionStart.isEmpty()) {
System.out.println("Could not find heading for major version: " + majorHeading);
System.out.println("Is this a new major version? If yes, please add an empty section for it manually to the Changelog");
System.exit(-1);
}

f.insert(changes, sectionStart.getAsInt() + 1);

Files.writeString(fileName, f.toString(), StandardCharsets.UTF_8);
}

static class Lines {

private final List<String> lines;

public Lines(List<String> lines) {
this.lines = new ArrayList<>(lines);
}

int lineCount() {
return lines.size();
}

OptionalInt findLine(Predicate<String> condition, int startAt) {
for (int i = startAt; i < lines.size(); i++) {
if (condition.test(lines.get(i))) {
return OptionalInt.of(i);
}
}
return OptionalInt.empty();
}

Lines cut(int startInclusive, int endExclusive) {
List<String> cutLines = new ArrayList<>();
for (int i = startInclusive; i < endExclusive; i++) {
cutLines.add(lines.remove(startInclusive));
}
return new Lines(cutLines);
}

void insert(Lines other, int insertAt) {
this.lines.addAll(insertAt, other.lines);
}

/**
* Trims lines consisting of only blanks at the top and bottom
*/
void trim() {
while (!lines.isEmpty() && lines.get(0).matches("\\s*")) {
lines.remove(0);
}
while (!lines.isEmpty() && lines.get(lines.size() - 1).matches("\\s*")) {
lines.remove(lines.size() - 1);
}
}

@Override
public String toString() {
return String.join("\n", lines);
}

public String getLine(int number) {
return lines.get(number);
}
}

}
227 changes: 227 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
---
# Releases the agent
name: Release

on:
workflow_dispatch:
inputs:
branch:
description: 'The branch to release'
required: true
default: 'main'
version:
description: 'The version to release (e.g. 1.2.3). This workflow will automatically perform the required version bumps'
required: true
update_changelog:
description: |
If enabled, everything in the changelog from the "Unreleased" section will be automatically moved to a new section for the new release.
If disabled, the changelog needs to be prepared for the release manually before triggering this workflow.
type: boolean
required: true
default: true

env:
JAVA_VERSION: 17
JAVA_DIST: adopt
MAVEN_CONFIG: >-
-V
-B
-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn
-Dhttps.protocols=TLSv1.2 -Dmaven.wagon.http.retryHandler.count=3
-Dmaven.wagon.httpconnectionManager.ttlSeconds=25

jobs:
prepare_release:
name: "Changelog and Version Bump"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
#TODO: Use apmmachine GH user + setup push authorization (SSH?)
with:
ref: ${{ inputs.branch }}
- name: Set up git user
run: |
git config user.name todo-github-actions
git config user.email todo-github-actions@github.com
- name: Set up JDK ${{ env.JAVA_VERSION }}
uses: actions/setup-java@v3
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: ${{ env.JAVA_DIST }}
cache: 'maven'
- name: Prepare changelog for release
if: ${{ inputs.update_changelog }}
run: |
java .ci/ReleaseChangelog.java CHANGELOG.asciidoc ${{ inputs.version }}
git commit -m "Prepare changelog for release ${{ inputs.version }}" CHANGELOG.asciidoc
- name: Bump version and add git tag
run: ./mvnw release:prepare -B -DpushChanges=false "-Darguments=-DskipTests -Dmaven.javadoc.skip=true" -DreleaseVersion=${{ inputs.version }}
- run: git push --atomic origin ${{ inputs.branch }} v${{ inputs.version }}


maven_central_deploy:
name: "Deploy to Maven Central (Buildkite)"
runs-on: ubuntu-latest
needs:
- prepare_release

# TODO: Run the deployment on Buildkite
# Buildkite needs to
# - checkout the tag "v${{ inputs.version }}" (which is created by the prepare_release job)
# - run ./mvnw -s .ci/settings.xml -Pgpg clean deploy -DskipTests --batch-mode
# Note that the previous ./mvnw release:prepare release:perform from jenkins are not required anymore:
# prepare_release does all the fiddling with the git history
steps:
- run: echo "Not implemented yet!"

await_artifact_on_maven_central:
name: "Wait for artifacts to be available on maven central"
runs-on: ubuntu-latest
needs:
- maven_central_deploy
steps:
- uses: actions/checkout@v3
- shell: bash
run: |
until .ci/release/wait_maven_artifact_published.sh ${{ inputs.version }}
do
echo "Artifacts not found on maven central. Sleeping 30 seconds, retrying afterwards"
sleep 30s
done


update_major_branch:
name: "Update Major Branch"
runs-on: ubuntu-latest
needs:
- await_artifact_on_maven_central
steps:
- uses: actions/checkout@v3
with:
ref: main
#TODO: Use apmmachine GH user + setup authorization (SSH?)
- run: .ci/release/update_major_branch.sh ${{ inputs.version }}
- run: git push -f origin "$(echo '${{ inputs.version }}' | sed -E 's/\..+/.x/')"

update_cloudfoundry:
name: "Update Cloudfoundry"
runs-on: ubuntu-latest
needs:
- await_artifact_on_maven_central
steps:
- uses: actions/checkout@v3
with:
ref: main
#TODO: Use apmmachine + setup authorization (SSH?)
- name: Set up git user
run: |
git config user.name todo-github-actions
git config user.email todo-github-actions@github.com
- name: "Update Cloudfoundry index.yml file"
shell: bash
run: .ci/release/update_cloudfoundry.sh ${{ inputs.version }}
- run: git push origin main


build_docker_images:
name: "Build and push docker images"
runs-on: ubuntu-latest
needs:
- await_artifact_on_maven_central
env:
TAG_NAME: v${{ inputs.version }}
SONATYPE_FALLBACK: 1
steps:
- uses: actions/checkout@v3
with:
ref: main
fetch-depth: 0 # Load entire history as it is required for the push-script
- name: "Build docker image"
shell: bash
# TODO: add docker login
run: |
./scripts/jenkins/build_docker.sh
./scripts/jenkins/push_docker.sh

publish_aws_lambda:
name: "Publish AWS Lambda"
runs-on: ubuntu-latest
needs:
- await_artifact_on_maven_central
outputs:
arn_content: ${{ steps.arn_output.outputs.arn_content }}
steps:
- uses: actions/checkout@v3
with:
ref: main
# TODO: actual lambda creation + upload
- name: Setup dummy ARN file
run: |
{
echo "### ARNs of the APM Java Agent's AWS Lambda Layer"
echo ''
echo '|Region|ARN|'
echo '|------|---|'
} > ".ci/arn-file.md"
- uses: actions/upload-artifact@v3
with:
name: arn-file
path: .ci/arn-file.md
- name: Add ARN file to output
id: arn_output
run: |
echo 'arn_content<<ARN_CONTENT_EOF' >> $GITHUB_OUTPUT
cat .ci/arn-file.md >> $GITHUB_OUTPUT
echo 'ARN_CONTENT_EOF' >> $GITHUB_OUTPUT


create_github_release:
name: "Create GitHub Release"
needs:
- publish_aws_lambda
- update_major_branch
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
ref: main
- name: Await release-notes published
shell: bash
run: |
until .ci/release/wait_release_notes_published.sh ${{ inputs.version }}
do
echo "Release notes not published yet. Sleeping 30 seconds, retrying afterwards"
sleep 30s
done
- name: Compute major.x branch
id: get_dotx_branch
run: echo "dotx_branch=$(echo '${{ inputs.version }}' | sed -E 's/\..+/.x/')" >> $GITHUB_OUTPUT
- uses: ncipollo/release-action@v1
with:
#token: TODO: set apmmachine (or whoever releases) user token here!
tag: v${{ inputs.version }}
name: Release ${{ inputs.version }}
body: |
[Release Notes for ${{ inputs.version }}](https://www.elastic.co/guide/en/apm/agent/java/current/release-notes-${{ steps.get_dotx_branch.outputs.dotx_branch }}.html#release-notes-${{ inputs.version }})
${{ needs.publish_aws_lambda.outputs.arn_content }}

bump_opbeans:
name: "Bump Opbeans agent version"
runs-on: ubuntu-latest
needs:
- await_artifact_on_maven_central
steps:
#TODO: Use apmmachine + setup authorization (SSH?)
- uses: actions/checkout@v3
with:
repository: JonasKunz/opbeans-java
ref: main
- name: Set up git user
run: |
git config user.name todo-github-actions
git config user.email todo-github-actions@github.com
- name: Bump version
run: |
.ci/bump-version.sh ${{ inputs.version }}
git tag v${{ inputs.version }}
- run: git push --atomic origin main v${{ inputs.version }}
3 changes: 0 additions & 3 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ endif::[]

=== Unreleased

[[release-notes-1.37.0]]
==== 1.37.0 - YYYY/MM/DD

[float]
===== Features
* Add the <<config-disable-outgoing-tracecontext-headers>> config option to disable injection of `tracecontext` on outgoing
Expand Down
4 changes: 2 additions & 2 deletions scripts/jenkins/push_docker.sh
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ if [ ${WORKERS+x} ] # We are on a CI worker
then
retry $RETRIES docker push $DOCKER_PUSH_IMAGE || echo "Push failed after $RETRIES retries"
else # We are in a local (non-CI) environment
docker push $DOCKER_PUSH_IMAGE || echo "You may need to run 'docker login' first and then re-run this script"
docker push $DOCKER_PUSH_IMAGE || { echo "You may need to run 'docker login' first and then re-run this script"; exit 1; }
fi

readonly LATEST_TAG=$(git tag --list --sort=version:refname "v*" | grep -v RC | sed s/^v// | tail -n 1)
Expand All @@ -62,6 +62,6 @@ then
then
retry $RETRIES docker push $DOCKER_PUSH_IMAGE_LATEST || echo "Push failed after $RETRIES retries"
else # We are in a local (non-CI) environment
docker push $DOCKER_PUSH_IMAGE_LATEST || echo "You may need to run 'docker login' first and then re-run this script"
docker push $DOCKER_PUSH_IMAGE_LATEST || { echo "You may need to run 'docker login' first and then re-run this script"; exit 1; }
fi
fi