Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

read config file path from TEAMSCALE_JAVA_PROFILER_CONFIG_FILE #425

Merged
merged 7 commits into from Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -5,6 +5,7 @@ We use [semantic versioning](http://semver.org/):
- PATCH version when you make backwards compatible bug fixes.

# Next Release
- [feature] Read configuration file path from `TEAMSCALE_JAVA_PROFILER_CONFIG_FILE` environment variable
- [feature] add installer for Windows
- [feature] Docker: agent copies itself to `/transfer` if this is mounted into the container
- [fix] Disable warning about proxy port not being correct when no proxy port was set at all
Expand Down
5 changes: 3 additions & 2 deletions agent/README.md
Expand Up @@ -51,8 +51,9 @@ The following options are available:
- `out` (optional): the path to a writable directory where the generated coverage XML files will be stored. (For details
see path format section below). Defaults to the subdirectory `coverage` inside the agent's installation directory.
- `config-file` (optional): a file which contains one or more of the previously named options as `key=value` entries
which are separated by line breaks. The file may also contain comments starting with `#`. (For details see path format
section below)
which are separated by line breaks. The file may also contain comments starting with `#`. For details see path format
section below.
Alternatively you can also set the `TEAMSCALE_JAVA_PROFILER_CONFIG_FILE` environment variable to that value.
- `config-id` (optional): a profiler configuration ID as defined in Teamscale. This allows to centrally manage the
profiler configuration in Teamscale's UI (under Project Configuration > Profilers since Teamscale 9.4).
Alternatively you can also set the `TEAMSCALE_JAVA_PROFILER_CONFIG_ID` environment variable to that value.
Expand Down
22 changes: 15 additions & 7 deletions agent/src/main/java/com/teamscale/jacoco/agent/PreMain.java
Expand Up @@ -42,9 +42,13 @@ public class PreMain {
*/
private static final String LOCKING_SYSTEM_PROPERTY = "TEAMSCALE_JAVA_PROFILER_ATTACHED";

/** Environment variable from which to read the config file to use. */
/** Environment variable from which to read the config ID to use.
* This is an ID for a profiler configuration that is stored in Teamscale. */
private static final String CONFIG_ID_ENVIRONMENT_VARIABLE = "TEAMSCALE_JAVA_PROFILER_CONFIG_ID";

/** Environment variable from which to read the config file to use. */
private static final String CONFIG_FILE_ENVIRONMENT_VARIABLE = "TEAMSCALE_JAVA_PROFILER_CONFIG_FILE";

/**
* Entry point for the agent, called by the JVM.
*/
Expand All @@ -55,15 +59,16 @@ public static void premain(String options, Instrumentation instrumentation) thro
System.setProperty(LOCKING_SYSTEM_PROPERTY, "true");

String environmentConfigId = System.getenv(CONFIG_ID_ENVIRONMENT_VARIABLE);
if (StringUtils.isEmpty(options) && environmentConfigId == null) {
String environmentConfigFile = System.getenv(CONFIG_FILE_ENVIRONMENT_VARIABLE);
if (StringUtils.isEmpty(options) && environmentConfigId == null && environmentConfigFile == null) {
// profiler was registered globally and no config was set explicitly by the user, thus ignore this process
// and don't profile anything
return;
}

karottenreibe marked this conversation as resolved.
Show resolved Hide resolved
AgentOptions agentOptions;
try {
agentOptions = getAndApplyAgentOptions(options, environmentConfigId);
agentOptions = getAndApplyAgentOptions(options, environmentConfigId, environmentConfigFile);
} catch (AgentOptionReceiveException e) {
// When Teamscale is not available, we don't want to fail hard to still allow for testing even if no
// coverage is collected (see TS-33237)
Expand All @@ -85,8 +90,9 @@ public static void premain(String options, Instrumentation instrumentation) thro
}

@NotNull
private static AgentOptions getAndApplyAgentOptions(String options,
String environmentConfigId) throws AgentOptionParseException, IOException, AgentOptionReceiveException {
private static AgentOptions getAndApplyAgentOptions(String options, String environmentConfigId,
String environmentConfigFile) throws AgentOptionParseException, IOException, AgentOptionReceiveException {

DelayedLogger delayedLogger = new DelayedLogger();
List<String> javaAgents = CollectionUtils.filter(ManagementFactory.getRuntimeMXBean().getInputArguments(),
s -> s.contains("-javaagent"));
Expand All @@ -103,7 +109,7 @@ private static AgentOptions getAndApplyAgentOptions(String options,
}
AgentOptions agentOptions;
try {
agentOptions = AgentOptionsParser.parse(options, environmentConfigId, credentials, delayedLogger);
agentOptions = AgentOptionsParser.parse(options, environmentConfigId, environmentConfigFile, credentials, delayedLogger);
} catch (AgentOptionParseException e) {
try (LoggingUtils.LoggingResources ignored = initializeFallbackLogging(options, delayedLogger)) {
delayedLogger.errorAndStdErr("Failed to parse agent options: " + e.getMessage(), e);
Expand All @@ -112,7 +118,9 @@ private static AgentOptions getAndApplyAgentOptions(String options,
}
} catch (AgentOptionReceiveException e) {
try (LoggingUtils.LoggingResources ignored = initializeFallbackLogging(options, delayedLogger)) {
delayedLogger.errorAndStdErr( e.getMessage() + " The application should start up normally, but NO coverage will be collected!", e);
delayedLogger.errorAndStdErr(
e.getMessage() + " The application should start up normally, but NO coverage will be collected!",
e);
attemptLogAndThrow(delayedLogger);
throw e;
}
Expand Down
Expand Up @@ -50,27 +50,30 @@ public class AgentOptionsParser {
private final FilePatternResolver filePatternResolver;
private final TeamscaleConfig teamscaleConfig;
private final String environmentConfigId;
private final String environmentConfigFile;
private final TeamscaleCredentials credentials;

/**
* Parses the given command-line options.
*
* @param environmentConfigId The Profiler configuration ID given via the
* {@link com.teamscale.jacoco.agent.PreMain#CONFIG_ID_ENVIRONMENT_VARIABLE} environment
* variable.
* @param environmentConfigId The Profiler configuration ID given via an environment variable.
* @param environmentConfigFile The Profiler configuration file given via an environment variable.
*/
public static AgentOptions parse(String optionsString, String environmentConfigId,
public static AgentOptions parse(String optionsString, String environmentConfigId, String environmentConfigFile,
TeamscaleCredentials credentials,
ILogger logger) throws AgentOptionParseException, AgentOptionReceiveException {
return new AgentOptionsParser(logger, environmentConfigId, credentials).parse(optionsString);
return new AgentOptionsParser(logger, environmentConfigId, environmentConfigFile, credentials).parse(
optionsString);
}

@VisibleForTesting
AgentOptionsParser(ILogger logger, String environmentConfigId, TeamscaleCredentials credentials) {
AgentOptionsParser(ILogger logger, String environmentConfigId, String environmentConfigFile,
TeamscaleCredentials credentials) {
this.logger = logger;
this.filePatternResolver = new FilePatternResolver(logger);
this.teamscaleConfig = new TeamscaleConfig(logger, filePatternResolver);
this.environmentConfigId = environmentConfigId;
this.environmentConfigFile = environmentConfigFile;
this.credentials = credentials;
}

Expand Down Expand Up @@ -99,9 +102,7 @@ public static AgentOptions parse(String optionsString, String environmentConfigI
}
}

if (environmentConfigId != null) {
handleOption(options, "config-id=" + environmentConfigId);
}
handleConfigFromEnvironment(options);

Validator validator = options.getValidator();
if (!validator.isValid()) {
Expand All @@ -110,6 +111,23 @@ public static AgentOptions parse(String optionsString, String environmentConfigI
return options;
}

private void handleConfigFromEnvironment(
AgentOptions options) throws AgentOptionParseException, AgentOptionReceiveException {
if (environmentConfigId != null) {
handleOption(options, "config-id=" + environmentConfigId);
}

if (environmentConfigFile != null) {
handleOption(options, "config-file=" + environmentConfigFile);
}

if (environmentConfigId != null && environmentConfigFile != null) {
logger.warn("You specified both an ID for a profiler configuration in Teamscale and a config file." +
" The config file will override the Teamscale configuration." +
" Please use one or the other.");
}
}

/**
* Parses and stores the given option in the format <code>key=value</code>.
*/
Expand Down Expand Up @@ -267,7 +285,8 @@ private void readConfigFromTeamscale(AgentOptions options,
options.teamscaleServer.userName,
options.teamscaleServer.userAccessToken);
options.configurationViaTeamscale = configuration;
logger.debug("Received the following options from Teamscale: " + configuration.getProfilerConfiguration().configurationOptions);
logger.debug(
"Received the following options from Teamscale: " + configuration.getProfilerConfiguration().configurationOptions);
readConfigFromString(options, configuration.getProfilerConfiguration().configurationOptions);
}

Expand Down
Expand Up @@ -15,6 +15,8 @@
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;
Expand All @@ -25,13 +27,15 @@
public class AgentOptionsParserTest {

private TeamscaleCredentials teamscaleCredentials;
private final AgentOptionsParser parser = new AgentOptionsParser(new CommandLineLogger(), null, null);
private final AgentOptionsParser parser = new AgentOptionsParser(new CommandLineLogger(), null, null, null);
private Path configFile;
/** The mock server to run requests against. */
protected MockWebServer mockWebServer;

/** Starts the mock server. */
@BeforeEach
public void setup() throws Exception {
configFile = Paths.get(getClass().getResource("agent.properties").toURI());
mockWebServer = new MockWebServer();
mockWebServer.start();
teamscaleCredentials = new TeamscaleCredentials(mockWebServer.url("/"), "user", "key");
Expand Down Expand Up @@ -68,7 +72,7 @@ public void testUploadMethodRecognition() throws Exception {
@Test
public void testUploadMethodRecognitionWithTeamscaleProperties() throws Exception {
TeamscaleCredentials credentials = new TeamscaleCredentials(HttpUrl.get("http://localhost"), "user", "key");
AgentOptionsParser parser = new AgentOptionsParser(new CommandLineLogger(), null, credentials);
AgentOptionsParser parser = new AgentOptionsParser(new CommandLineLogger(), null, null, credentials);

assertThat(parser.parse(null).determineUploadMethod()).isEqualTo(AgentOptions.EUploadMethod.LOCAL_DISK);
assertThat(parser.parse("azure-url=azure.com,azure-key=key").determineUploadMethod()).isEqualTo(
Expand All @@ -91,20 +95,44 @@ public void testUploadMethodRecognitionWithTeamscaleProperties() throws Exceptio
}

@Test
public void environmentConfigOverridesCommandLineOptions() throws Exception {
public void environmentConfigIdOverridesCommandLineOptions() throws Exception {
ProfilerRegistration registration = new ProfilerRegistration();
registration.profilerId = UUID.randomUUID().toString();
registration.profilerConfiguration = new ProfilerConfiguration();
registration.profilerConfiguration.configurationId = "my-config";
registration.profilerConfiguration.configurationOptions = "teamscale-partition=foo";
mockWebServer.enqueue(new MockResponse().setBody(JsonUtils.serialize(registration)));
AgentOptionsParser parser = new AgentOptionsParser(new CommandLineLogger(), "my-config",
teamscaleCredentials);
null, teamscaleCredentials);
AgentOptions options = parser.parse("teamscale-partition=bar");

assertThat(options.teamscaleServer.partition).isEqualTo("foo");
}

@Test
public void environmentConfigFileOverridesCommandLineOptions() throws Exception {
AgentOptionsParser parser = new AgentOptionsParser(new CommandLineLogger(), null, configFile.toString(),
teamscaleCredentials);
AgentOptions options = parser.parse("teamscale-partition=from-command-line");

assertThat(options.teamscaleServer.partition).isEqualTo("from-config-file");
}

@Test
public void environmentConfigFileOverridesConfigId() throws Exception {
ProfilerRegistration registration = new ProfilerRegistration();
registration.profilerId = UUID.randomUUID().toString();
registration.profilerConfiguration = new ProfilerConfiguration();
registration.profilerConfiguration.configurationId = "my-config";
registration.profilerConfiguration.configurationOptions = "teamscale-partition=from-config-id";
mockWebServer.enqueue(new MockResponse().setBody(JsonUtils.serialize(registration)));
AgentOptionsParser parser = new AgentOptionsParser(new CommandLineLogger(), "my-config", configFile.toString(),
teamscaleCredentials);
AgentOptions options = parser.parse("teamscale-partition=from-command-line");

assertThat(options.teamscaleServer.partition).isEqualTo("from-config-file");
}

@Test
public void notAllRequiredTeamscaleOptionsSet() {
assertThatCode(
Expand Down Expand Up @@ -183,7 +211,8 @@ public void revisionOrCommitRequireProject() {
public void environmentConfigIdDoesNotExist() {
mockWebServer.enqueue(new MockResponse().setResponseCode(404).setBody("invalid-config-id does not exist"));
assertThatThrownBy(
() -> new AgentOptionsParser(new CommandLineLogger(), "invalid-config-id", teamscaleCredentials).parse(
() -> new AgentOptionsParser(new CommandLineLogger(), "invalid-config-id", null,
teamscaleCredentials).parse(
"")
).isInstanceOf(AgentOptionParseException.class).hasMessageContaining("invalid-config-id does not exist");
}
Expand All @@ -203,7 +232,7 @@ public void mustPreserveDefaultExcludes() throws Exception {

@Test
public void teamscalePropertiesCredentialsUsedAsDefaultButOverridable() throws Exception {
AgentOptionsParser parser = new AgentOptionsParser(new CommandLineLogger(), null, teamscaleCredentials);
AgentOptionsParser parser = new AgentOptionsParser(new CommandLineLogger(), null, null, teamscaleCredentials);

assertThat(parser.parse("teamscale-project=p,teamscale-partition=p").teamscaleServer.userName).isEqualTo(
"user");
Expand Down
Expand Up @@ -347,7 +347,7 @@ private static Predicate<String> excludeFilter(String filterString) throws Excep
}

private static AgentOptionsParser getAgentOptionsParserWithDummyLogger() {
return new AgentOptionsParser(new CommandLineLogger(), null, null);
return new AgentOptionsParser(new CommandLineLogger(), null, null, null);
}

/**
Expand Down
@@ -0,0 +1 @@
teamscale-partition=from-config-file