Skip to content

Commit d36e7dc

Browse files
authored
Adding support for latest version to attcher cli (#2659)
1 parent d1aca85 commit d36e7dc

File tree

8 files changed

+173
-11
lines changed

8 files changed

+173
-11
lines changed

CHANGELOG.asciidoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ the agent will now include the database name in the dependency, thus `mysql/my-d
3636
* Added support for the new service target fields - {pull}2578[#2578]
3737
* Capture the database name from JDBC connection string - {pull}2642[#2642]
3838
* Added an additional span around Javalin template renderers - {pull}2381[#2381]
39+
* Added support for downloading the latest agent version through the attach CLI by setting `--download-agent-version latest`. In
40+
addition, when using the `apm-agent-attach-cli-slim.jar`, which does not contain a bundled agent, the latest version will be downloaded
41+
from maven at runtime unless configured otherwise through `--download-agent-version` - {pull}2659[#2659]
3942
4043
[float]
4144
===== Bug fixes

apm-agent-attach-cli/src/main/java/co/elastic/apm/attach/AgentAttacher.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
*/
5454
public class AgentAttacher {
5555

56+
public static final String LATEST_VERSION = "latest";
5657
// intentionally not static so that we can initLogging first
5758
private final Logger logger = LogManager.getLogger(AgentAttacher.class);
5859
private final Arguments arguments;
@@ -121,6 +122,10 @@ public static void main(String[] args) {
121122
Logger logger = initLogging(arguments);
122123

123124
String downloadAgentVersion = arguments.getDownloadAgentVersion();
125+
if (downloadAgentVersion == null && arguments.getAgentJar() == null) {
126+
// If there is no bundled agent and a path for agent was not specified, default to download the latest
127+
downloadAgentVersion = LATEST_VERSION;
128+
}
124129
if (downloadAgentVersion != null) {
125130
try {
126131
downloadAndVerifyAgent(arguments, downloadAgentVersion);
@@ -148,6 +153,9 @@ public static void main(String[] args) {
148153
}
149154

150155
private static void downloadAndVerifyAgent(Arguments arguments, String downloadAgentVersion) throws Exception {
156+
if (downloadAgentVersion.equalsIgnoreCase(LATEST_VERSION)) {
157+
downloadAgentVersion = AgentDownloader.findLatestVersion();
158+
}
151159
PgpSignatureVerifier pgpSignatureVerifier;
152160
try {
153161
Path targetLibDir = AgentDownloadUtils.of(downloadAgentVersion).getTargetLibDir();
@@ -543,6 +551,7 @@ static void printHelp(PrintStream out) {
543551
out.println();
544552
out.println(" --download-agent-version <agent-version>");
545553
out.println(" Instead of the bundled agent jar, download and attach the specified agent version from maven.");
554+
out.println(" <agent-version> can be either the explicit version (for example: `1.15.0`) or `latest`.");
546555
}
547556

548557
Map<String, String> getConfig() {

apm-agent-attach-cli/src/main/java/co/elastic/apm/attach/AgentDownloader.java

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,23 @@
1818
*/
1919
package co.elastic.apm.attach;
2020

21+
import co.elastic.apm.agent.util.Version;
2122
import org.apache.logging.log4j.LogManager;
2223
import org.apache.logging.log4j.Logger;
2324

25+
import java.io.BufferedReader;
2426
import java.io.IOException;
2527
import java.io.InputStream;
28+
import java.io.InputStreamReader;
2629
import java.net.HttpURLConnection;
2730
import java.net.URL;
2831
import java.nio.file.Files;
2932
import java.nio.file.Path;
3033
import java.util.Locale;
3134
import java.util.Properties;
35+
import java.util.TreeSet;
36+
import java.util.regex.Matcher;
37+
import java.util.regex.Pattern;
3238

3339
/**
3440
* A utility for downloading any given version of the Elastic APM Java agent from maven central.
@@ -37,6 +43,7 @@
3743
*/
3844
public class AgentDownloader {
3945

46+
private static final String VERSION_EXTRACTION_REGEX = "<a href.+>([0-9]+.[0-9.]+.[0-9.]+)/</a>";
4047
private static final String AGENT_GROUP_ID = "co.elastic.apm";
4148
private static final String AGENT_ARTIFACT_ID = "elastic-apm-agent";
4249
private static final String CLI_JAR_VERSION;
@@ -90,7 +97,7 @@ public AgentDownloader(PgpSignatureVerifier pgpSignatureVerifier) {
9097
*/
9198
Path downloadAndVerifyAgent(String agentVersion) throws Exception {
9299
logger.debug("Requested to download Elastic APM Java agent version {}", agentVersion);
93-
final String mavenAgentBaseUrl = getAgentMavenBaseUrl(agentVersion);
100+
final String mavenAgentBaseUrl = getAgentMavenVersionBaseUrl(agentVersion);
94101
Path targetDir = AgentDownloadUtils.of(agentVersion).getTargetAgentDir();
95102
String agentJarName = computeAgentJarName(agentVersion);
96103
String mavenAgentJarUrl = computeFileUrl(mavenAgentBaseUrl, agentJarName);
@@ -133,28 +140,39 @@ String computeAgentJarName(String agentVersion) {
133140
/**
134141
* Returns the url for an Elastic APM Agent jar in maven.
135142
*/
136-
String getAgentMavenBaseUrl(String agentVersion) throws Exception {
137-
final String groupId = AGENT_GROUP_ID.replace(".", "/");
138-
final String agentMavenUrl = String.format("https://repo1.maven.org/maven2/%s/%s/%s", groupId, AGENT_ARTIFACT_ID, agentVersion);
143+
String getAgentMavenVersionBaseUrl(String agentVersion) throws Exception {
144+
final String agentMavenUrl = String.format("%s/%s", getAgentArtifactMavenBaseUrl(), agentVersion);
139145
if (!verifyUrl(agentMavenUrl)) {
140146
throw new IllegalArgumentException(String.format("Cannot find maven URL for version %s, make sure provided version is valid", agentVersion));
141147
}
142148
return agentMavenUrl;
143149
}
144150

151+
/**
152+
* Returns the url for the Elastic APM Agent jar artifact in maven.
153+
*/
154+
static String getAgentArtifactMavenBaseUrl() throws Exception {
155+
final String groupId = AGENT_GROUP_ID.replace(".", "/");
156+
final String agentMavenUrl = String.format("https://repo1.maven.org/maven2/%s/%s", groupId, AGENT_ARTIFACT_ID);
157+
if (!verifyUrl(agentMavenUrl)) {
158+
throw new IllegalArgumentException(String.format("Cannot find maven URL for agent artifact: %s:%s", groupId, AGENT_ARTIFACT_ID));
159+
}
160+
return agentMavenUrl;
161+
}
162+
145163
/**
146164
* Returns {@code true} if the given url exists, and {@code false} otherwise.
147165
* <p>
148166
* The given url must be {@code https} and existing means a {@code HEAD} request returns 200.
149167
*/
150-
private boolean verifyUrl(String urlString) throws IOException {
168+
private static boolean verifyUrl(String urlString) throws IOException {
151169
HttpURLConnection urlConnection = openConnection(urlString);
152170
urlConnection.setRequestMethod("HEAD");
153171
urlConnection.connect();
154172
return urlConnection.getResponseCode() == 200;
155173
}
156174

157-
private HttpURLConnection openConnection(String urlString) throws IOException {
175+
private static HttpURLConnection openConnection(String urlString) throws IOException {
158176
URL url = new URL(urlString);
159177
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
160178
urlConnection.addRequestProperty("User-Agent", USER_AGENT);
@@ -201,6 +219,34 @@ void verifyPgpSignature(final Path agentJarFile, final String mavenAgentUrlStrin
201219
logger.info("Elastic APM Java Agent jar PGP signature successfully verified.");
202220
}
203221

222+
static String findLatestVersion() throws Exception {
223+
String agentArtifactMavenBaseUrl = getAgentArtifactMavenBaseUrl();
224+
HttpURLConnection httpURLConnection = openConnection(agentArtifactMavenBaseUrl);
225+
TreeSet<Version> versions = parseMavenArtifactHtml(httpURLConnection.getInputStream());
226+
if (versions.isEmpty()) {
227+
throw new IllegalStateException("Failed to parse agent versions from the contents of " + agentArtifactMavenBaseUrl);
228+
}
229+
return versions.last().toString();
230+
}
231+
232+
static TreeSet<Version> parseMavenArtifactHtml(InputStream htmlInputStream) throws IOException {
233+
TreeSet<Version> versions = new TreeSet<>();
234+
BufferedReader versionsHtmlReader = new BufferedReader(new InputStreamReader(htmlInputStream));
235+
Pattern pattern = Pattern.compile(VERSION_EXTRACTION_REGEX);
236+
String line;
237+
while ((line = versionsHtmlReader.readLine()) != null) {
238+
try {
239+
Matcher matcher = pattern.matcher(line);
240+
if (matcher.find()) {
241+
versions.add(Version.of(matcher.group(1)));
242+
}
243+
} catch (Exception e) {
244+
// ignore, probably a regex false positive
245+
}
246+
}
247+
return versions;
248+
}
249+
204250
/**
205251
* Return the public key ID of our agent signing key.
206252
*

apm-agent-attach-cli/src/test/java/co/elastic/apm/attach/AgentDownloaderTest.java

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,16 @@
1818
*/
1919
package co.elastic.apm.attach;
2020

21+
import co.elastic.apm.agent.util.Version;
2122
import co.elastic.apm.attach.bouncycastle.BouncyCastleVerifier;
2223
import org.junit.jupiter.api.Test;
2324

25+
import java.io.IOException;
2426
import java.nio.file.FileAlreadyExistsException;
2527
import java.nio.file.Files;
2628
import java.nio.file.Path;
2729
import java.util.Locale;
30+
import java.util.TreeSet;
2831

2932
import static org.assertj.core.api.Assertions.assertThat;
3033
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -39,21 +42,21 @@ class AgentDownloaderTest {
3942

4043
@Test
4144
void testMavenUrl() throws Exception {
42-
String mavenUrl = agentDownloader.getAgentMavenBaseUrl("1.24.0");
45+
String mavenUrl = agentDownloader.getAgentMavenVersionBaseUrl("1.24.0");
4346
assertThat(mavenUrl).isNotNull();
4447
assertThat(mavenUrl).isEqualTo("https://repo1.maven.org/maven2/co/elastic/apm/elastic-apm-agent/1.24.0");
4548
}
4649

4750
@Test
4851
void testWrongVersion() {
49-
assertThatThrownBy(() -> agentDownloader.getAgentMavenBaseUrl("1.24.1")).isInstanceOf(IllegalArgumentException.class);
50-
assertThatThrownBy(() -> agentDownloader.getAgentMavenBaseUrl("error")).isInstanceOf(IllegalArgumentException.class);
52+
assertThatThrownBy(() -> agentDownloader.getAgentMavenVersionBaseUrl("1.24.1")).isInstanceOf(IllegalArgumentException.class);
53+
assertThatThrownBy(() -> agentDownloader.getAgentMavenVersionBaseUrl("error")).isInstanceOf(IllegalArgumentException.class);
5154
}
5255

5356
@Test
5457
void testDownloadFile() throws Exception {
5558
String agentVersion = "1.25.0";
56-
String mavenAgentBaseUrl = agentDownloader.getAgentMavenBaseUrl(agentVersion);
59+
String mavenAgentBaseUrl = agentDownloader.getAgentMavenVersionBaseUrl(agentVersion);
5760
String agentPgpSignatureFileName = String.format(Locale.ROOT, "%s.asc", agentDownloader.computeAgentJarName(agentVersion));
5861
String mavenPgpSignatureUrl = agentDownloader.computeFileUrl(mavenAgentBaseUrl, agentPgpSignatureFileName);
5962
System.out.println("mavenPgpSignatureUrl = " + mavenPgpSignatureUrl);
@@ -68,7 +71,7 @@ void testDownloadFile() throws Exception {
6871

6972
@Test
7073
void testDownloadAndVerifyAgent() throws Exception {
71-
String agentVersion = "1.25.0";
74+
String agentVersion = AgentDownloader.findLatestVersion();
7275
Path targetDir = AgentDownloadUtils.of(agentVersion).getTargetAgentDir();
7376
final Path localAgentPath = targetDir.resolve(agentDownloader.computeAgentJarName(agentVersion));
7477
System.out.println("localAgentPath = " + localAgentPath);
@@ -91,4 +94,20 @@ void testDownloadAgentAndFailVerification() throws Exception {
9194
assertThatThrownBy(() -> spyAgentDownloader.downloadAndVerifyAgent(agentVersion)).isInstanceOf(IllegalStateException.class);
9295
assertThat(Files.exists(localAgentPath)).isFalse();
9396
}
97+
98+
@Test
99+
void testLatestVersion() throws Exception {
100+
String latestVersion = AgentDownloader.findLatestVersion();
101+
System.out.println("latestVersion = " + latestVersion);
102+
assertThat(latestVersion).isNotEmpty();
103+
}
104+
105+
@Test
106+
void testAgentArtifactMavenPageParsing() throws IOException {
107+
TreeSet<Version> versions = AgentDownloader.parseMavenArtifactHtml(AgentDownloaderTest.class.getResourceAsStream(
108+
"/MavenCentral_agent_artifact.html"));
109+
assertThat(versions).hasSize(50);
110+
assertThat(versions.first().toString()).isEqualTo("0.5.1");
111+
assertThat(versions.last().toString()).isEqualTo("1.31.0");
112+
}
94113
}

0 commit comments

Comments
 (0)