From 09590116120de8e745bfd60b25b2cb5ffd187c07 Mon Sep 17 00:00:00 2001 From: Fabian Streitel Date: Tue, 15 Apr 2025 16:28:32 +0200 Subject: [PATCH 1/3] read access token from environment variable --- .../com/teamscale/jacoco/agent/PreMain.java | 7 +++- .../agent/options/AgentOptionsParser.java | 34 +++++++++------- .../options/TeamscalePropertiesUtils.java | 7 ++-- .../agent/options/AgentOptionsParserTest.java | 39 ++++++++++++++----- .../agent/options/AgentOptionsTest.java | 4 +- 5 files changed, 62 insertions(+), 29 deletions(-) diff --git a/agent/src/main/java/com/teamscale/jacoco/agent/PreMain.java b/agent/src/main/java/com/teamscale/jacoco/agent/PreMain.java index 87138cf50..9393101fd 100644 --- a/agent/src/main/java/com/teamscale/jacoco/agent/PreMain.java +++ b/agent/src/main/java/com/teamscale/jacoco/agent/PreMain.java @@ -55,6 +55,9 @@ public class PreMain { /** Environment variable from which to read the config file to use. */ private static final String CONFIG_FILE_ENVIRONMENT_VARIABLE = "TEAMSCALE_JAVA_PROFILER_CONFIG_FILE"; + /** Environment variable from which to read the Teamscale access token. */ + private static final String ACCESS_TOKEN_ENVIRONMENT_VARIABLE = "TEAMSCALE_ACCESS_TOKEN"; + /** * Entry point for the agent, called by the JVM. */ @@ -138,11 +141,13 @@ private static Pair> getAndApplyAgentOptions(Strin "No explicit teamscale.properties file given. Looking for Teamscale credentials in a config file or via a command line argument. This is expected unless the installer based setup was used."); } + String environmentAccessToken = System.getenv(ACCESS_TOKEN_ENVIRONMENT_VARIABLE); + Pair> parseResult; AgentOptions agentOptions; try { parseResult = AgentOptionsParser.parse( - options, environmentConfigId, environmentConfigFile, credentials, + options, environmentConfigId, environmentConfigFile, credentials, environmentAccessToken, delayedLogger); agentOptions = parseResult.getFirst(); } catch (AgentOptionParseException e) { diff --git a/agent/src/main/java/com/teamscale/jacoco/agent/options/AgentOptionsParser.java b/agent/src/main/java/com/teamscale/jacoco/agent/options/AgentOptionsParser.java index fa8f6d6d7..18762553b 100644 --- a/agent/src/main/java/com/teamscale/jacoco/agent/options/AgentOptionsParser.java +++ b/agent/src/main/java/com/teamscale/jacoco/agent/options/AgentOptionsParser.java @@ -62,34 +62,40 @@ public class AgentOptionsParser { private final TeamscaleConfig teamscaleConfig; private final String environmentConfigId; private final String environmentConfigFile; + @Nullable private final TeamscaleCredentials credentials; + @Nullable + private final String environmentAccessToken; private final List collectedErrors; /** * Parses the given command-line options. * - * @param environmentConfigId The Profiler configuration ID given via an environment variable. - * @param environmentConfigFile The Profiler configuration file given via an environment variable. + * @param environmentConfigId The Profiler configuration ID given via an environment variable. + * @param environmentConfigFile The Profiler configuration file given via an environment variable. + * @param credentials Optional Teamscale credentials from a teamscale.properties file. + * @param environmentAccessToken Optional access token for accessing Teamscale, read from an env variable. */ public static Pair> parse(String optionsString, String environmentConfigId, - String environmentConfigFile, - @Nullable TeamscaleCredentials credentials, + String environmentConfigFile, @Nullable TeamscaleCredentials credentials, + @Nullable String environmentAccessToken, ILogger logger) throws AgentOptionParseException, AgentOptionReceiveException { AgentOptionsParser parser = new AgentOptionsParser(logger, environmentConfigId, environmentConfigFile, - credentials); + credentials, environmentAccessToken); AgentOptions options = parser.parse(optionsString); return Pair.createPair(options, parser.getCollectedErrors()); } @VisibleForTesting AgentOptionsParser(ILogger logger, String environmentConfigId, String environmentConfigFile, - TeamscaleCredentials credentials) { + @Nullable TeamscaleCredentials credentials, @Nullable String environmentAccessToken) { this.logger = logger; this.filePatternResolver = new FilePatternResolver(logger); this.teamscaleConfig = new TeamscaleConfig(logger, filePatternResolver); this.environmentConfigId = environmentConfigId; this.environmentConfigFile = environmentConfigFile; this.credentials = credentials; + this.environmentAccessToken = environmentAccessToken; this.collectedErrors = Lists.newArrayList(); } @@ -126,6 +132,9 @@ public void throwOnCollectedErrors() throws Exception { options.teamscaleServer.userName = credentials.userName; options.teamscaleServer.userAccessToken = credentials.accessKey; } + if (environmentAccessToken != null) { + options.teamscaleServer.userAccessToken = environmentAccessToken; + } if (!StringUtils.isEmpty(optionsString)) { String[] optionParts = optionsString.split(","); @@ -179,7 +188,7 @@ private void handleConfigId(AgentOptions options) throws AgentOptionReceiveExcep readConfigFromTeamscale(options); } - private void handleConfigFile(AgentOptions options) throws AgentOptionParseException, AgentOptionReceiveException { + private void handleConfigFile(AgentOptions options) throws AgentOptionParseException { if (environmentConfigFile != null) { handleOptionPart(options, "config-file=" + environmentConfigFile); } @@ -195,7 +204,7 @@ private void handleConfigFile(AgentOptions options) throws AgentOptionParseExcep * Parses and stores the given option in the format key=value. */ private void handleOptionPart(AgentOptions options, - String optionPart) throws AgentOptionParseException, AgentOptionReceiveException { + String optionPart) throws AgentOptionParseException { Pair keyAndValue = parseOption(optionPart); handleOption(options, keyAndValue.getFirst(), keyAndValue.getSecond()); } @@ -204,7 +213,7 @@ private void handleOptionPart(AgentOptions options, * Parses and stores the option with the given key and value. */ private void handleOption(AgentOptions options, - String key, String value) throws AgentOptionParseException, AgentOptionReceiveException { + String key, String value) throws AgentOptionParseException { if (key.startsWith(DEBUG)) { handleDebugOption(options, value); return; @@ -299,7 +308,7 @@ private Pair parseOption(String optionPart) throws AgentOptionPa * @return true if it has successfully processed the given option. */ private boolean handleAgentOptions(AgentOptions options, String key, String value) - throws AgentOptionParseException, AgentOptionReceiveException { + throws AgentOptionParseException { switch (key) { case "config-id": options.teamscaleServer.configId = value; @@ -447,7 +456,7 @@ public static > T parseEnumValue(String key, String value, Cla * excludes=third.party.* */ private void readConfigFromFile(AgentOptions options, - File configFile) throws AgentOptionParseException, AgentOptionReceiveException { + File configFile) throws AgentOptionParseException { try { String content = FileSystemUtils.readFileUTF8(configFile); readConfigFromString(options, content); @@ -460,8 +469,7 @@ private void readConfigFromFile(AgentOptions options, } } - private void readConfigFromString(AgentOptions options, - String content) throws AgentOptionParseException, AgentOptionReceiveException { + private void readConfigFromString(AgentOptions options, String content) { List configFileKeyValues = org.conqat.lib.commons.string.StringUtils.splitLinesAsList( content); for (String optionKeyValue : configFileKeyValues) { diff --git a/agent/src/main/java/com/teamscale/jacoco/agent/options/TeamscalePropertiesUtils.java b/agent/src/main/java/com/teamscale/jacoco/agent/options/TeamscalePropertiesUtils.java index 423db572f..966552893 100644 --- a/agent/src/main/java/com/teamscale/jacoco/agent/options/TeamscalePropertiesUtils.java +++ b/agent/src/main/java/com/teamscale/jacoco/agent/options/TeamscalePropertiesUtils.java @@ -19,8 +19,8 @@ public class TeamscalePropertiesUtils { .resolve("teamscale.properties"); /** - * Tries to open {@link #TEAMSCALE_PROPERTIES_PATH} and parse that properties file to obtain {@link - * TeamscaleCredentials}. + * Tries to open {@link #TEAMSCALE_PROPERTIES_PATH} and parse that properties file to obtain + * {@link TeamscaleCredentials}. * * @return the parsed credentials or null in case the teamscale.properties file doesn't exist. * @throws AgentOptionParseException in case the teamscale.properties file exists but can't be read or parsed. @@ -32,7 +32,8 @@ public static TeamscaleCredentials parseCredentials() throws AgentOptionParseExc /** * Same as {@link #parseCredentials()} but testable since the path is not hardcoded. */ - /*package*/ static TeamscaleCredentials parseCredentials( + /*package*/ + static TeamscaleCredentials parseCredentials( Path teamscalePropertiesPath) throws AgentOptionParseException { if (!Files.exists(teamscalePropertiesPath)) { return null; diff --git a/agent/src/test/java/com/teamscale/jacoco/agent/options/AgentOptionsParserTest.java b/agent/src/test/java/com/teamscale/jacoco/agent/options/AgentOptionsParserTest.java index 1ccf18140..263b323b1 100644 --- a/agent/src/test/java/com/teamscale/jacoco/agent/options/AgentOptionsParserTest.java +++ b/agent/src/test/java/com/teamscale/jacoco/agent/options/AgentOptionsParserTest.java @@ -72,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, null, credentials); + AgentOptionsParser parser = new AgentOptionsParser(new CommandLineLogger(), null, null, credentials, null); assertThat(parseAndThrow(null).determineUploadMethod()).isEqualTo(AgentOptions.EUploadMethod.LOCAL_DISK); assertThat(parseAndThrow("azure-url=azure.com,azure-key=key").determineUploadMethod()).isEqualTo( @@ -103,7 +103,7 @@ public void environmentConfigIdOverridesCommandLineOptions() throws Exception { registration.profilerConfiguration.configurationOptions = "teamscale-partition=foo"; mockWebServer.enqueue(new MockResponse().setBody(JsonUtils.serialize(registration))); AgentOptionsParser parser = new AgentOptionsParser(new CommandLineLogger(), "my-config", - null, teamscaleCredentials); + null, teamscaleCredentials, null); AgentOptions options = parseAndThrow(parser, "teamscale-partition=bar"); assertThat(options.teamscaleServer.partition).isEqualTo("foo"); @@ -112,7 +112,7 @@ public void environmentConfigIdOverridesCommandLineOptions() throws Exception { @Test public void environmentConfigFileOverridesCommandLineOptions() throws Exception { AgentOptionsParser parser = new AgentOptionsParser(new CommandLineLogger(), null, configFile.toString(), - teamscaleCredentials); + teamscaleCredentials, null); AgentOptions options = parseAndThrow(parser, "teamscale-partition=from-command-line"); assertThat(options.teamscaleServer.partition).isEqualTo("from-config-file"); @@ -127,7 +127,7 @@ public void environmentConfigFileOverridesConfigId() throws Exception { 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); + teamscaleCredentials, null); AgentOptions options = parseAndThrow(parser, "teamscale-partition=from-command-line"); assertThat(options.teamscaleServer.partition).isEqualTo("from-config-file"); @@ -245,11 +245,22 @@ public void environmentConfigIdDoesNotExist() { mockWebServer.enqueue(new MockResponse().setResponseCode(404).setBody("invalid-config-id does not exist")); assertThatThrownBy( () -> new AgentOptionsParser(new CommandLineLogger(), "invalid-config-id", null, - teamscaleCredentials).parse( + teamscaleCredentials, null).parse( "") ).isInstanceOf(AgentOptionReceiveException.class).hasMessageContaining("invalid-config-id does not exist"); } + @Test + public void accessTokenFromEnvironment() throws Exception { + assertThat(parseAndThrow( + "teamscale-server-url=teamscale.com,teamscale-user=user,teamscale-partition=p,teamscale-project=p", + "envtoken").teamscaleServer.userAccessToken).isEqualTo("envtoken"); + // command line overrides env variable + assertThat(parseAndThrow( + "teamscale-server-url=teamscale.com,teamscale-user=user,teamscale-access-token=commandlinetoken,teamscale-partition=p,teamscale-project=p", + "envtoken").teamscaleServer.userAccessToken).isEqualTo("commandlinetoken"); + } + @Test public void notGivingAnyOptionsShouldBeOK() throws Exception { parseAndThrow(""); @@ -265,11 +276,13 @@ public void mustPreserveDefaultExcludes() throws Exception { @Test public void teamscalePropertiesCredentialsUsedAsDefaultButOverridable() throws Exception { - assertThat(parseAndThrow(new AgentOptionsParser(new CommandLineLogger(), null, null, teamscaleCredentials), - "teamscale-project=p,teamscale-partition=p").teamscaleServer.userName).isEqualTo( + assertThat( + parseAndThrow(new AgentOptionsParser(new CommandLineLogger(), null, null, teamscaleCredentials, null), + "teamscale-project=p,teamscale-partition=p").teamscaleServer.userName).isEqualTo( "user"); - assertThat(parseAndThrow(new AgentOptionsParser(new CommandLineLogger(), null, null, teamscaleCredentials), - "teamscale-project=p,teamscale-partition=p,teamscale-user=user2").teamscaleServer.userName).isEqualTo( + assertThat( + parseAndThrow(new AgentOptionsParser(new CommandLineLogger(), null, null, teamscaleCredentials, null), + "teamscale-project=p,teamscale-partition=p,teamscale-user=user2").teamscaleServer.userName).isEqualTo( "user2"); } @@ -280,7 +293,13 @@ private AgentOptions parseAndThrow(AgentOptionsParser parser, String options) th } private AgentOptions parseAndThrow(String options) throws Exception { - AgentOptionsParser parser = new AgentOptionsParser(new CommandLineLogger(), null, null, null); + AgentOptionsParser parser = new AgentOptionsParser(new CommandLineLogger(), null, null, null, null); + return parseAndThrow(parser, options); + } + + private AgentOptions parseAndThrow(String options, String environmentAccessToken) throws Exception { + AgentOptionsParser parser = new AgentOptionsParser(new CommandLineLogger(), null, null, null, + environmentAccessToken); return parseAndThrow(parser, options); } diff --git a/agent/src/test/java/com/teamscale/jacoco/agent/options/AgentOptionsTest.java b/agent/src/test/java/com/teamscale/jacoco/agent/options/AgentOptionsTest.java index b7f03e4ad..14b259ba2 100644 --- a/agent/src/test/java/com/teamscale/jacoco/agent/options/AgentOptionsTest.java +++ b/agent/src/test/java/com/teamscale/jacoco/agent/options/AgentOptionsTest.java @@ -482,11 +482,11 @@ private static Predicate excludeFilter(String filterString) throws Excep } private static AgentOptionsParser getAgentOptionsParserWithDummyLogger() { - return new AgentOptionsParser(new CommandLineLogger(), null, null, null); + return new AgentOptionsParser(new CommandLineLogger(), null, null, null, null); } private static AgentOptionsParser getAgentOptionsParserWithDummyLoggerAndCredentials(TeamscaleCredentials credentials) { - return new AgentOptionsParser(new CommandLineLogger(), null, null, credentials); + return new AgentOptionsParser(new CommandLineLogger(), null, null, credentials, null); } /** From 2ce808d404aaa94ddf6a454bd59789f106605a8c Mon Sep 17 00:00:00 2001 From: Fabian Streitel Date: Tue, 15 Apr 2025 16:32:42 +0200 Subject: [PATCH 2/3] update README and CHANGELOG --- CHANGELOG.md | 2 ++ agent/README.md | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4bbee6e0..c02dd5957 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ We use [semantic versioning](http://semver.org/): - PATCH version when you make backwards compatible bug fixes. # Next version +- [feature] _agent_: Access token can be passed via environment variable `TEAMSCALE_ACCESS_TOKEN` + # 35.1.0 - [feature] _agent_: Experimental support for Java 25 class files - [fix] _agent_: Reported wrong version number in docker container diff --git a/agent/README.md b/agent/README.md index e02d56739..ba5702daa 100644 --- a/agent/README.md +++ b/agent/README.md @@ -137,8 +137,9 @@ patterns with `*`, `**` and `?`. - `teamscale-user`: the username used to authenticate against Teamscale. The user account must have the "Perform External Uploads" permission on the given project. - `teamscale-access-token`: the access token of the user. + Alternatively you can also set the `TEAMSCALE_ACCESS_TOKEN` environment variable to that value. - `teamscale-partition`: the partition within Teamscale to upload coverage to. A partition can be an arbitrary string - which can be used to encode e.g. the test environment or the tester. These can be individually toggled on or off in + which can be used to encode e.g. the test environment. These can be individually toggled on or off in Teamscale's UI. - `teamscale-revision`: the source control revision (e.g. SVN revision or Git hash) that has been used to build the system under test. Teamscale uses this to map the coverage to the corresponding source code. For an alternative see `teamscale-revision-manifest-jar`. From 516552e028d287d021699c6f4152136a76cf3129 Mon Sep 17 00:00:00 2001 From: Fabian Streitel Date: Tue, 15 Apr 2025 16:35:10 +0200 Subject: [PATCH 3/3] resolve finding --- .../agent/options/AgentOptionsParser.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/agent/src/main/java/com/teamscale/jacoco/agent/options/AgentOptionsParser.java b/agent/src/main/java/com/teamscale/jacoco/agent/options/AgentOptionsParser.java index 18762553b..1a16faa43 100644 --- a/agent/src/main/java/com/teamscale/jacoco/agent/options/AgentOptionsParser.java +++ b/agent/src/main/java/com/teamscale/jacoco/agent/options/AgentOptionsParser.java @@ -127,14 +127,7 @@ public void throwOnCollectedErrors() throws Exception { AgentOptions options = new AgentOptions(logger); options.originalOptionsString = optionsString; - if (credentials != null) { - options.teamscaleServer.url = credentials.url; - options.teamscaleServer.userName = credentials.userName; - options.teamscaleServer.userAccessToken = credentials.accessKey; - } - if (environmentAccessToken != null) { - options.teamscaleServer.userAccessToken = environmentAccessToken; - } + presetCredentialOptions(options); if (!StringUtils.isEmpty(optionsString)) { String[] optionParts = optionsString.split(","); @@ -162,6 +155,17 @@ public void throwOnCollectedErrors() throws Exception { return options; } + private void presetCredentialOptions(AgentOptions options) { + if (credentials != null) { + options.teamscaleServer.url = credentials.url; + options.teamscaleServer.userName = credentials.userName; + options.teamscaleServer.userAccessToken = credentials.accessKey; + } + if (environmentAccessToken != null) { + options.teamscaleServer.userAccessToken = environmentAccessToken; + } + } + /** * Stores the agent options for proxies in the {@link TeamscaleProxySystemProperties} and overwrites the password * with the password found in the proxy-password-file if necessary.