Skip to content

Commit 058e8a6

Browse files
[*] CI: publish to maven
1 parent 381ef79 commit 058e8a6

4 files changed

Lines changed: 227 additions & 1 deletion

File tree

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ jobs:
152152
run: >
153153
./gradlew :java:timebase:aerondirect:publish :java:timebase:commons:publish :java:timebase:s3:publish
154154
:java:timebase:pub:publish :java:timebase:api:publish :java:timebase:computations-api:publish
155-
:java:timebase:computations-std:publish :java:timebase:qql:publish :java:timebase:client:publish
155+
:java:timebase:computations-std:publish :java:timebase:qql:publish :java:timebase:client:publish uploadArtifactsToMaven
156156
env:
157157
SONATYPE_REPOSITORY: ${{ secrets.SONATYPE_REPOSITORY }}
158158
SONATYPE_NEXUS_USERNAME: ${{ secrets.SONATYPE_NEXUS_USERNAME }}

buildSrc/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ dependencies {
1515
compile 'com.fasterxml.jackson.core:jackson-core:2.13.5'
1616

1717
compile 'de.undercouch:gradle-download-task:3.4.3'
18+
compile 'org.json:json:20240303'
1819
testCompile 'junit:junit:4.13.1'
1920
}
2021

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
package deltix.buildtools;
2+
3+
/*
4+
* Copyright 2014-2025 Real Logic Limited.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
import org.gradle.api.DefaultTask;
20+
import org.gradle.api.provider.Property;
21+
import org.gradle.api.tasks.Input;
22+
import org.gradle.api.tasks.TaskAction;
23+
import org.json.JSONArray;
24+
import org.json.JSONObject;
25+
26+
import java.io.BufferedReader;
27+
import java.io.IOException;
28+
import java.io.InputStream;
29+
import java.io.InputStreamReader;
30+
import java.net.HttpURLConnection;
31+
import java.net.URI;
32+
import java.net.URL;
33+
import java.nio.charset.StandardCharsets;
34+
import java.util.Base64;
35+
36+
/**
37+
* This task performs manual steps to publish artifacts to Central Portal via OSSRH Staging API.
38+
*/
39+
public class SonatypeCentralPortalUploadRepositoryTask extends DefaultTask {
40+
private static final String CENTRAL_PORTAL_OSSRH_API_URI = "https://ossrh-staging-api.central.sonatype.com";
41+
private static final int CONNECTION_TIMEOUT = 30000;
42+
43+
private final Property<String> portalUsername;
44+
private final Property<String> portalPassword;
45+
private final Property<String> groupId;
46+
private final Property<Boolean> snapshotRelease;
47+
48+
/**
49+
* Create new task instance.
50+
*/
51+
public SonatypeCentralPortalUploadRepositoryTask() {
52+
portalUsername = getProject().getObjects().property(String.class);
53+
portalPassword = getProject().getObjects().property(String.class);
54+
groupId = getProject().getObjects().property(String.class);
55+
snapshotRelease = getProject().getObjects().property(Boolean.class);
56+
}
57+
58+
/**
59+
* Return property to set Central Portal username.
60+
*
61+
* @return Central Portal username.
62+
*/
63+
@Input
64+
public Property<String> getPortalUsername() {
65+
return portalUsername;
66+
}
67+
68+
/**
69+
* Return property to set Central Portal password.
70+
*
71+
* @return Central Portal password.
72+
*/
73+
@Input
74+
public Property<String> getPortalPassword() {
75+
return portalPassword;
76+
}
77+
78+
/**
79+
* Return property to set {@code groupId} of the project.
80+
*
81+
* @return {@code groupId} of the project.
82+
*/
83+
@Input
84+
public Property<String> getGroupId() {
85+
return groupId;
86+
}
87+
88+
/**
89+
* Return property to set snapshot release.
90+
*
91+
* @return {@code true} if snapshot release.
92+
*/
93+
@Input
94+
public Property<Boolean> getSnapshotRelease() {
95+
return snapshotRelease;
96+
}
97+
98+
/**
99+
* Publish staging repository to the Central Portal.
100+
*/
101+
@TaskAction
102+
public void run() throws IOException, InterruptedException {
103+
if (!portalUsername.isPresent()) {
104+
return;
105+
}
106+
107+
if (snapshotRelease.get()) {
108+
return;
109+
}
110+
111+
String userNameAndPassword = portalUsername.get() + ":" + portalPassword.get();
112+
String bearer = Base64.getEncoder().encodeToString(userNameAndPassword.getBytes(StandardCharsets.US_ASCII));
113+
URI apiUri = URI.create(CENTRAL_PORTAL_OSSRH_API_URI);
114+
115+
String repositoryKey = findOpenRepository(apiUri, bearer);
116+
uploadRepositoryToPortal(apiUri, bearer, repositoryKey);
117+
dropRepository(apiUri, bearer, repositoryKey);
118+
}
119+
120+
private String findOpenRepository(URI apiUri, String bearer) throws IOException {
121+
String endpoint = apiUri.resolve("/manual/search/repositories?ip=client").toString();
122+
HttpURLConnection conn = (HttpURLConnection) new URL(endpoint).openConnection();
123+
conn.setConnectTimeout(CONNECTION_TIMEOUT);
124+
conn.setReadTimeout(CONNECTION_TIMEOUT);
125+
conn.setRequestMethod("GET");
126+
conn.setRequestProperty("Authorization", "Bearer " + bearer);
127+
128+
int status = conn.getResponseCode();
129+
String body = readBody(conn);
130+
if (status != 200) {
131+
throw new IllegalStateException("Failed to query repositories: " +
132+
"status=" + status + ", response=" + body);
133+
}
134+
135+
JSONArray repositories = new JSONObject(body).getJSONArray("repositories");
136+
if (repositories.isEmpty()) {
137+
throw new IllegalStateException("No open repositories found!");
138+
}
139+
140+
String repositoryKey = null;
141+
String group = groupId.get();
142+
for (int i = 0; i < repositories.length(); i++) {
143+
JSONObject repo = (JSONObject) repositories.get(i);
144+
if ("open".equals(repo.getString("state"))) {
145+
String key = repo.getString("key");
146+
if (key.contains(group)) {
147+
repositoryKey = key;
148+
break;
149+
}
150+
}
151+
}
152+
153+
if (null == repositoryKey) {
154+
throw new IllegalStateException("No open repositories found!");
155+
}
156+
return repositoryKey;
157+
}
158+
159+
private static void uploadRepositoryToPortal(URI apiUri, String bearer, String repositoryKey) throws IOException {
160+
String endpoint = apiUri.resolve("/manual/upload/repository/" + repositoryKey + "?publishing_type=user_managed").toString();
161+
HttpURLConnection conn = (HttpURLConnection) new URL(endpoint).openConnection();
162+
conn.setConnectTimeout(CONNECTION_TIMEOUT);
163+
conn.setReadTimeout(CONNECTION_TIMEOUT);
164+
conn.setRequestMethod("POST");
165+
conn.setRequestProperty("Authorization", "Bearer " + bearer);
166+
conn.setDoOutput(true);
167+
conn.getOutputStream().close();
168+
169+
int status = conn.getResponseCode();
170+
String body = readBody(conn);
171+
if (status != 200) {
172+
throw new IllegalStateException("Failed to upload repository: repository_key=" + repositoryKey + ", status=" + status + ", response=" + body);
173+
}
174+
}
175+
176+
private static void dropRepository(URI apiUri, String bearer, String repositoryKey) throws IOException {
177+
String endpoint = apiUri.resolve("/manual/drop/repository/" + repositoryKey).toString();
178+
HttpURLConnection conn = (HttpURLConnection) new URL(endpoint).openConnection();
179+
conn.setConnectTimeout(CONNECTION_TIMEOUT);
180+
conn.setReadTimeout(CONNECTION_TIMEOUT);
181+
conn.setRequestMethod("DELETE");
182+
conn.setRequestProperty("Authorization", "Bearer " + bearer);
183+
184+
int status = conn.getResponseCode();
185+
String body = readBody(conn);
186+
if (status != 204) {
187+
throw new IllegalStateException("Failed to drop repository: repository_key=" + repositoryKey + ", status=" + status + ", response=" + body);
188+
}
189+
}
190+
191+
private static String readBody(HttpURLConnection conn) throws IOException {
192+
InputStream stream;
193+
try {
194+
stream = (conn.getResponseCode() < 400) ? conn.getInputStream() : conn.getErrorStream();
195+
if (stream == null) {
196+
return "";
197+
}
198+
} catch (IOException e) {
199+
return "";
200+
}
201+
202+
StringBuilder body = new StringBuilder();
203+
try (BufferedReader in = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
204+
String line;
205+
while ((line = in.readLine()) != null) {
206+
body.append(line);
207+
}
208+
}
209+
return body.toString();
210+
}
211+
}

java/build.gradle

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import deltix.buildtools.SonatypeCentralPortalUploadRepositoryTask
2+
13
import java.nio.file.Files
24
import java.nio.file.Paths
35

@@ -548,6 +550,18 @@ configure(leafProjects) {
548550
}
549551
}
550552

553+
ext {
554+
sonaUser = findProperty('SONATYPE_NEXUS_USERNAME') ?: System.getenv('SONATYPE_NEXUS_USERNAME') ?: "FakeUser"
555+
sonaPass = findProperty('SONATYPE_NEXUS_PASSWORD') ?: System.getenv('SONATYPE_NEXUS_PASSWORD') ?: "FakePass"
556+
}
557+
558+
tasks.register('uploadArtifactsToMaven', SonatypeCentralPortalUploadRepositoryTask) {
559+
portalUsername.set(sonaUser)
560+
portalPassword.set(sonaPass)
561+
groupId.set('com.epam.deltix')
562+
snapshotRelease.set(false)
563+
}
564+
551565
def webAbbrs = [':java:timebase:web': 'default', ':java:timebase:webmonitor': 'tb']
552566
webAbbrs.each { key, value ->
553567
project(key) {

0 commit comments

Comments
 (0)