This is an automated email from the ASF dual-hosted git repository.
hboutemy pushed a commit to branch maven-atr-plugin
in repository https://gitbox.apache.org/repos/asf/maven-studies.git
The following commit(s) were added to refs/heads/maven-atr-plugin by this push:
new 07f8142a2 implement ATR upload API
07f8142a2 is described below
commit 07f8142a2d7509bb54c468c920c42a55582de1ed
Author: Hervé Boutemy <[email protected]>
AuthorDate: Sun Mar 15 12:36:48 2026 +0100
implement ATR upload API
---
src/it/apache-release-profile/pom.xml | 4 +-
src/it/apache-release-profile/verify.groovy | 6 +-
.../apache/maven/plugins/atr/AbstractAtrMojo.java | 12 ++
.../org/apache/maven/plugins/atr/AtrClient.java | 217 +++++++++++++++++++++
.../org/apache/maven/plugins/atr/UploadMojo.java | 12 +-
5 files changed, 243 insertions(+), 8 deletions(-)
diff --git a/src/it/apache-release-profile/pom.xml
b/src/it/apache-release-profile/pom.xml
index 2b8c16413..b2b17e929 100644
--- a/src/it/apache-release-profile/pom.xml
+++ b/src/it/apache-release-profile/pom.xml
@@ -27,7 +27,7 @@ under the License.
</parent>
<groupId>org.apache.maven.its.atr</groupId>
- <artifactId>test-apache-release</artifactId>
+ <artifactId>maven-atr-test-apache-release</artifactId>
<version>1.0-SNAPSHOT</version>
<name>ATR Plugin IT - Apache Release Profile</name>
@@ -67,6 +67,8 @@ under the License.
</goals>
<configuration>
<dryRun>true</dryRun>
+ <asfuid>asfuid</asfuid>
+ <token>some-token</token>
<project>${project.artifactId}</project>
<version>${project.version}</version>
<files>
diff --git a/src/it/apache-release-profile/verify.groovy
b/src/it/apache-release-profile/verify.groovy
index 21e37f62b..3bff8d272 100644
--- a/src/it/apache-release-profile/verify.groovy
+++ b/src/it/apache-release-profile/verify.groovy
@@ -20,9 +20,7 @@
// Verify that the build log contains evidence of the ATR plugin execution
def buildLog = new File(basedir, 'build.log')
assert buildLog.exists()
-assert buildLog.text.contains('DRY RUN: Simulating ATR upload')
-assert buildLog.text.contains('DRY RUN: Would upload:')
-assert buildLog.text.contains('Composing release
https://release-test.apache.org/compose/test-apache-release/1.0-SNAPSHOT')
-assert buildLog.text.contains('to
https://release-test.apache.org/file/test-apache-release/1.0-SNAPSHOT/test-apache-release-1.0-SNAPSHOT-source-release.zip')
+assert buildLog.text.contains('Composing release
https://release-test.apache.org/compose/maven-atr-test-apache-release/1.0-SNAPSHOT')
+assert buildLog.text.contains('to
https://release-test.apache.org/file/maven-atr-test-apache-release/1.0-SNAPSHOT/maven-atr-test-apache-release-1.0-SNAPSHOT-source-release.zip')
return true
diff --git a/src/main/java/org/apache/maven/plugins/atr/AbstractAtrMojo.java
b/src/main/java/org/apache/maven/plugins/atr/AbstractAtrMojo.java
index 660c80065..a1a3c338c 100644
--- a/src/main/java/org/apache/maven/plugins/atr/AbstractAtrMojo.java
+++ b/src/main/java/org/apache/maven/plugins/atr/AbstractAtrMojo.java
@@ -50,6 +50,18 @@ public abstract class AbstractAtrMojo extends AbstractMojo {
@Parameter(property = "atr.dryRun", defaultValue = "false")
protected boolean dryRun;
+ /**
+ * Personal Access Token (PAT) for ATR API authentication.
+ */
+ @Parameter(property = "atr.token", required = true)
+ protected String token;
+
+ /**
+ * ASF user ID for ATR API authentication.
+ */
+ @Parameter(property = "atr.asfuid", required = true)
+ protected String asfuid;
+
@Override
public final void execute() throws MojoExecutionException,
MojoFailureException {
if (skip) {
diff --git a/src/main/java/org/apache/maven/plugins/atr/AtrClient.java
b/src/main/java/org/apache/maven/plugins/atr/AtrClient.java
new file mode 100644
index 000000000..468e9e277
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/atr/AtrClient.java
@@ -0,0 +1,217 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugins.atr;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Base64;
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.logging.Log;
+
+/**
+ * Client for interacting with the ATR (Apache Test Release) API.
+ *
+ * @author Maven Team
+ */
+public class AtrClient {
+
+ private final URL baseUrl;
+ private final String pat;
+ private final String asfuid;
+ private final Log log;
+ private String jwt;
+
+ /**
+ * Create a new ATR client.
+ *
+ * @param baseUrl the base URL of the ATR server
+ * @param pat the Personal Access Token for authentication
+ * @param asfuid the ASF user ID
+ * @param log the Maven logger
+ */
+ public AtrClient(URL baseUrl, String pat, String asfuid, Log log) {
+ this.baseUrl = baseUrl;
+ this.pat = pat;
+ this.asfuid = asfuid;
+ this.log = log;
+ }
+
+ /**
+ * Create a JWT from the PAT.
+ *
+ * @throws MojoExecutionException if JWT creation fails
+ */
+ private void ensureJwt() throws MojoExecutionException {
+ if (jwt != null) {
+ return;
+ }
+
+ try {
+ // Build JSON request for JWT creation
+ String jsonRequest =
+ "{" + "\"asfuid\":\"" + escapeJson(asfuid) + "\"," +
"\"pat\":\"" + escapeJson(pat) + "\"" + "}";
+
+ // Create connection
+ URL jwtUrl = new URL(baseUrl, "api/jwt/create");
+ HttpURLConnection conn = (HttpURLConnection)
jwtUrl.openConnection();
+ conn.setRequestMethod("POST");
+ conn.setRequestProperty("Content-Type", "application/json");
+ conn.setDoOutput(true);
+
+ // Send request
+ try (OutputStream os = conn.getOutputStream()) {
+ os.write(jsonRequest.getBytes(StandardCharsets.UTF_8));
+ }
+
+ // Check response
+ int responseCode = conn.getResponseCode();
+ if (responseCode == HttpURLConnection.HTTP_OK || responseCode ==
HttpURLConnection.HTTP_CREATED) {
+ String response = readResponse(conn.getInputStream());
+ jwt = parseJwt(response);
+ log.debug("JWT created successfully");
+ } else {
+ String errorResponse = readResponse(conn.getErrorStream());
+ throw new MojoExecutionException("Failed to create JWT: HTTP "
+ responseCode + " - " + errorResponse);
+ }
+ } catch (IOException e) {
+ throw new MojoExecutionException("Failed to create JWT from PAT",
e);
+ }
+ }
+
+ /**
+ * Upload a file to ATR.
+ *
+ * @param project the project name
+ * @param version the version
+ * @param relpath the relative path within the release (e.g.,
"artifactId-version-source-release.zip")
+ * @param file the file to upload
+ * @return the revision number
+ * @throws MojoExecutionException if the upload fails
+ */
+ public String uploadFile(String project, String version, String relpath,
Path file) throws MojoExecutionException {
+ // Ensure we have a valid JWT
+ ensureJwt();
+ log.warn("upload to " + relpath);
+
+ try {
+ // Read file content and encode as base64
+ byte[] fileBytes = Files.readAllBytes(file);
+ String content = Base64.getEncoder().encodeToString(fileBytes);
+
+ // Build JSON request
+ String jsonRequest = buildUploadJsonRequest(project, version,
relpath, content);
+
+ // Create connection
+ URL uploadUrl = new URL(baseUrl, "api/release/upload");
+ HttpURLConnection conn = (HttpURLConnection)
uploadUrl.openConnection();
+ conn.setRequestMethod("POST");
+ conn.setRequestProperty("Content-Type", "application/json");
+ conn.setRequestProperty("Authorization", "Bearer " + jwt);
+ conn.setDoOutput(true);
+
+ // Send request
+ try (OutputStream os = conn.getOutputStream()) {
+ os.write(jsonRequest.getBytes(StandardCharsets.UTF_8));
+ }
+
+ // Check response
+ int responseCode = conn.getResponseCode();
+ if (responseCode == HttpURLConnection.HTTP_CREATED || responseCode
== HttpURLConnection.HTTP_ACCEPTED) {
+ String response = readResponse(conn.getInputStream());
+ log.debug("Upload successful: " + response);
+ return parseRevisionNumber(response);
+ } else {
+ String errorResponse = readResponse(conn.getErrorStream());
+ throw new MojoExecutionException("Failed to upload file: HTTP
" + responseCode + " - " + errorResponse);
+ }
+ } catch (IOException e) {
+ throw new MojoExecutionException("Failed to upload file to ATR: "
+ file, e);
+ }
+ }
+
+ private String buildUploadJsonRequest(String project, String version,
String relpath, String content) {
+ // Simple JSON construction - in production, consider using a JSON
library
+ return "{"
+ + "\"project\":\"" + escapeJson(project) + "\","
+ + "\"version\":\"" + escapeJson(version) + "\","
+ + "\"relpath\":\"" + escapeJson(relpath) + "\","
+ + "\"content\":\"" + escapeJson(content) + "\""
+ + "}";
+ }
+
+ private String escapeJson(String str) {
+ return str.replace("\\", "\\\\")
+ .replace("\"", "\\\"")
+ .replace("\n", "\\n")
+ .replace("\r", "\\r")
+ .replace("\t", "\\t");
+ }
+
+ private String readResponse(InputStream is) throws IOException {
+ if (is == null) {
+ return "";
+ }
+ byte[] buffer = new byte[8192];
+ int bytesRead;
+ StringBuilder response = new StringBuilder();
+ while ((bytesRead = is.read(buffer)) != -1) {
+ response.append(new String(buffer, 0, bytesRead,
StandardCharsets.UTF_8));
+ }
+ return response.toString();
+ }
+
+ private String parseRevisionNumber(String jsonResponse) {
+ // Simple JSON parsing - extract revision number
+ // In production, consider using a JSON library
+ int numberIndex = jsonResponse.indexOf("\"number\"");
+ if (numberIndex != -1) {
+ int startQuote = jsonResponse.indexOf("\"", numberIndex + 9);
+ if (startQuote != -1) {
+ int endQuote = jsonResponse.indexOf("\"", startQuote + 1);
+ if (endQuote != -1) {
+ return jsonResponse.substring(startQuote + 1, endQuote);
+ }
+ }
+ }
+ return "unknown";
+ }
+
+ private String parseJwt(String jsonResponse) {
+ // Simple JSON parsing - extract JWT token
+ // In production, consider using a JSON library
+ int jwtIndex = jsonResponse.indexOf("\"jwt\"");
+ if (jwtIndex != -1) {
+ int startQuote = jsonResponse.indexOf("\"", jwtIndex + 6);
+ if (startQuote != -1) {
+ int endQuote = jsonResponse.indexOf("\"", startQuote + 1);
+ if (endQuote != -1) {
+ return jsonResponse.substring(startQuote + 1, endQuote);
+ }
+ }
+ }
+ throw new IllegalArgumentException("Failed to parse JWT from response:
" + jsonResponse);
+ }
+}
diff --git a/src/main/java/org/apache/maven/plugins/atr/UploadMojo.java
b/src/main/java/org/apache/maven/plugins/atr/UploadMojo.java
index 20b212ca9..c091bac5c 100644
--- a/src/main/java/org/apache/maven/plugins/atr/UploadMojo.java
+++ b/src/main/java/org/apache/maven/plugins/atr/UploadMojo.java
@@ -92,9 +92,15 @@ public class UploadMojo extends AbstractAtrMojo {
getLog().info("Uploading: " + file.getFileName() + " to " +
getAtrFileUrl(file));
- // TODO: Implement ATR upload logic for single file
- // This will integrate with the ATR CLI (atr upload) functionality
- // to upload Apache distribution artifacts to release-test.apache.org
+ // Build target path on ATR space
+ String target =
+ (directory != null ? directory + "/" : "") +
file.getFileName().toString();
+
+ // Upload using ATR client
+ AtrClient client = new AtrClient(url, token, asfuid, getLog());
+ String revisionNumber = client.uploadFile(project, version, target,
file);
+
+ getLog().info("Upload successful. Revision: " + revisionNumber);
}
/**