88import static org .assertj .core .api .Assertions .assertThat ;
99
1010import com .google .errorprone .annotations .CanIgnoreReturnValue ;
11+ import java .io .IOException ;
12+ import java .nio .file .Files ;
1113import java .nio .file .Path ;
1214import java .time .Duration ;
1315import java .util .ArrayList ;
14- import java .util .Arrays ;
15- import java .util .Collections ;
16+ import java .util .HashMap ;
1617import java .util .HashSet ;
1718import java .util .List ;
1819import java .util .Locale ;
20+ import java .util .Map ;
1921import java .util .Set ;
22+ import java .util .stream .Collectors ;
2023import org .testcontainers .containers .GenericContainer ;
2124import org .testcontainers .containers .wait .strategy .Wait ;
2225import org .testcontainers .utility .MountableFile ;
@@ -35,6 +38,19 @@ public class JmxScraperContainer extends GenericContainer<JmxScraperContainer> {
3538 private TestKeyStore keyStore ;
3639 private TestKeyStore trustStore ;
3740 private boolean sslRmiRegistry ;
41+ private ConfigSource configSource ;
42+
43+ /** Defines different strategies to provide scraper configuration */
44+ public enum ConfigSource {
45+ /** system properties with "-D" prefix in JVM command */
46+ SYSTEM_PROPERTIES ,
47+ /** properties file */
48+ PROPERTIES_FILE ,
49+ /** standard input */
50+ STDIN ,
51+ /** environment variables with "OTEL_" prefix, non-otel options as system properties */
52+ ENVIRONMENT_VARIABLES ;
53+ }
3854
3955 public JmxScraperContainer (String otlpEndpoint , String baseImage ) {
4056 super (baseImage );
@@ -48,6 +64,7 @@ public JmxScraperContainer(String otlpEndpoint, String baseImage) {
4864 this .targetSystems = new HashSet <>();
4965 this .customYamlFiles = new HashSet <>();
5066 this .extraJars = new ArrayList <>();
67+ this .configSource = ConfigSource .SYSTEM_PROPERTIES ;
5168 }
5269
5370 /**
@@ -182,83 +199,185 @@ public JmxScraperContainer withSslRmiRegistry() {
182199 return this ;
183200 }
184201
202+ /**
203+ * Sets how configuration is provided to scraper
204+ *
205+ * @param source configuration source
206+ * @return this
207+ */
208+ @ CanIgnoreReturnValue
209+ public JmxScraperContainer withConfigSource (ConfigSource source ) {
210+ this .configSource = source ;
211+ return this ;
212+ }
213+
185214 @ Override
186215 public void start () {
187- // for now only configure through JVM args
188- List <String > arguments = new ArrayList <>();
189- arguments .add ("java" );
190- arguments .add ("-Dotel.metrics.exporter=otlp" );
191- arguments .add ("-Dotel.exporter.otlp.endpoint=" + endpoint );
216+
217+ Map <String , String > config = new HashMap <>();
218+ config .put ("otel.metrics.exporter" , "otlp" );
219+ config .put ("otel.exporter.otlp.endpoint" , endpoint );
192220
193221 if (!targetSystems .isEmpty ()) {
194- arguments . add ( "-Dotel .jmx.target.system=" + String .join ("," , targetSystems ));
222+ config . put ( "otel .jmx.target.system" , String .join ("," , targetSystems ));
195223 }
196224
197225 if (serviceUrl == null ) {
198226 throw new IllegalStateException ("Missing service URL" );
199227 }
200- arguments .add ("-Dotel.jmx.service.url=" + serviceUrl );
228+ config .put ("otel.jmx.service.url" , serviceUrl );
229+
201230 // always use a very short export interval for testing
202- arguments . add ( "-Dotel .metric.export.interval= 1s" );
231+ config . put ( "otel .metric.export.interval" , " 1s" );
203232
204233 if (user != null ) {
205- arguments . add ( "-Dotel .jmx.username=" + user );
234+ config . put ( "otel .jmx.username" , user );
206235 }
207236 if (password != null ) {
208- arguments . add ( "-Dotel .jmx.password=" + password );
237+ config . put ( "otel .jmx.password" , password );
209238 }
210239
211- arguments . addAll ( addSecureStore (keyStore , /* isKeyStore= */ true ) );
212- arguments . addAll ( addSecureStore (trustStore , /* isKeyStore= */ false ) );
240+ addSecureStore (keyStore , /* isKeyStore= */ true , config );
241+ addSecureStore (trustStore , /* isKeyStore= */ false , config );
213242
214243 if (sslRmiRegistry ) {
215- arguments . add ( "-Dotel .jmx.remote.registry.ssl= true" );
244+ config . put ( "otel .jmx.remote.registry.ssl" , " true" );
216245 }
217246
218247 if (!customYamlFiles .isEmpty ()) {
219248 for (String yaml : customYamlFiles ) {
220249 this .withCopyFileToContainer (MountableFile .forClasspathResource (yaml ), yaml );
221250 }
222- arguments .add ("-Dotel.jmx.config=" + String .join ("," , customYamlFiles ));
251+ config .put ("otel.jmx.config" , String .join ("," , customYamlFiles ));
252+ }
253+
254+ List <String > cmd = new ArrayList <>();
255+ cmd .add ("java" );
256+
257+ switch (configSource ) {
258+ case SYSTEM_PROPERTIES :
259+ cmd .addAll (
260+ toKeyValueString (config ).stream ().map (s -> "-D" + s ).collect (Collectors .toList ()));
261+ break ;
262+ case PROPERTIES_FILE :
263+ try {
264+ Path configFile = Files .createTempFile ("config" , ".properties" );
265+ Files .write (configFile , toKeyValueString (config ));
266+ this .withCopyFileToContainer (MountableFile .forHostPath (configFile ), "/config.properties" );
267+ } catch (IOException e ) {
268+ throw new IllegalStateException (e );
269+ }
270+ break ;
271+ case STDIN :
272+ // nothing needed here
273+ break ;
274+ case ENVIRONMENT_VARIABLES :
275+ Map <String , String > env = new HashMap <>();
276+ Map <String , String > other = new HashMap <>();
277+ config .forEach (
278+ (k , v ) -> {
279+ if (k .startsWith ("otel." )) {
280+ env .put (k .toUpperCase (Locale .ROOT ).replace ("." , "_" ), v );
281+ } else {
282+ other .put (k , v );
283+ }
284+ });
285+
286+ if (!other .isEmpty ()) {
287+ env .put (
288+ "JAVA_TOOL_OPTIONS" ,
289+ toKeyValueString (other ).stream ().map (s -> "-D" + s ).collect (Collectors .joining (" " )));
290+ }
291+ this .withEnv (env );
292+ env .forEach ((k , v ) -> logger ().info ("Using environment variable {} = {} " , k , v ));
293+
294+ break ;
223295 }
224296
225297 if (extraJars .isEmpty ()) {
226298 // using "java -jar" to start
227- arguments .add ("-jar" );
228- arguments .add ("/scraper.jar" );
299+ cmd .add ("-jar" );
300+ cmd .add ("/scraper.jar" );
229301 } else {
230302 // using "java -cp" to start
231- arguments .add ("-cp" );
232- arguments .add ("/scraper.jar:" + String .join (":" , extraJars ));
233- arguments .add ("io.opentelemetry.contrib.jmxscraper.JmxScraper" );
303+ cmd .add ("-cp" );
304+ cmd .add ("/scraper.jar:" + String .join (":" , extraJars ));
305+ cmd .add ("io.opentelemetry.contrib.jmxscraper.JmxScraper" );
306+ }
307+
308+ switch (configSource ) {
309+ case SYSTEM_PROPERTIES :
310+ case ENVIRONMENT_VARIABLES :
311+ // no extra program argument needed
312+ break ;
313+ case PROPERTIES_FILE :
314+ cmd .add ("-config" );
315+ cmd .add ("/config.properties" );
316+ break ;
317+ case STDIN :
318+ cmd .add ("-config" );
319+ cmd .add ("-" );
320+ break ;
234321 }
235322
236323 if (testJmx ) {
237- arguments .add ("-test" );
324+ cmd .add ("-test" );
238325 this .waitingFor (Wait .forLogMessage (".*JMX connection test.*" , 1 ));
239326 } else {
240327 this .waitingFor (
241328 Wait .forLogMessage (".*JMX scraping started.*" , 1 )
242329 .withStartupTimeout (Duration .ofSeconds (10 )));
243330 }
244331
245- this .withCommand (arguments .toArray (new String [0 ]));
332+ if (configSource != ConfigSource .STDIN ) {
333+ this .withCommand (cmd .toArray (new String [0 ]));
334+ } else {
335+ // generate shell script to feed standard input with config
336+ List <String > lines = new ArrayList <>();
337+ lines .add ("#!/bin/bash" );
338+ lines .add (String .join (" " , cmd ) + "<<EOF" );
339+ lines .addAll (toKeyValueString (config ));
340+ lines .add ("EOF" );
341+
342+ Path script ;
343+ try {
344+ script = Files .createTempFile ("scraper" , ".sh" );
345+ Files .write (script , lines );
346+ } catch (IOException e ) {
347+ throw new IllegalStateException (e );
348+ }
246349
247- logger ().info ("Starting scraper with command: " + String .join (" " , arguments ));
350+ logger ().info ("Scraper executed with /scraper.sh shell script" );
351+ for (int i = 0 ; i < lines .size (); i ++) {
352+ logger ().info ("/scrapper.sh:{} {}" , i , lines .get (i ));
353+ }
248354
355+ this .withCopyFileToContainer (MountableFile .forHostPath (script , 500 ), "/scraper.sh" );
356+ this .withCommand ("/scraper.sh" );
357+ }
358+
359+ logger ().info ("Starting scraper with command: " + String .join (" " , this .getCommandParts ()));
249360 super .start ();
250361 }
251362
252- private List <String > addSecureStore (TestKeyStore keyStore , boolean isKeyStore ) {
363+ private void addSecureStore (
364+ TestKeyStore keyStore , boolean isKeyStore , Map <String , String > config ) {
253365 if (keyStore == null ) {
254- return Collections . emptyList () ;
366+ return ;
255367 }
256368 Path path = keyStore .getPath ();
257369 String containerPath = "/" + path .getFileName ().toString ();
258370 this .withCopyFileToContainer (MountableFile .forHostPath (path ), containerPath );
259371
260- String prefix = String .format ("-Djavax.net.ssl.%sStore" , isKeyStore ? "key" : "trust" );
261- return Arrays .asList (
262- prefix + "=" + containerPath , prefix + "Password=" + keyStore .getPassword ());
372+ String prefix = String .format ("javax.net.ssl.%sStore" , isKeyStore ? "key" : "trust" );
373+
374+ config .put (prefix , containerPath );
375+ config .put (prefix + "Password" , keyStore .getPassword ());
376+ }
377+
378+ private static List <String > toKeyValueString (Map <String , String > options ) {
379+ return options .entrySet ().stream ()
380+ .map (e -> String .format ("%s=%s" , e .getKey (), e .getValue ()))
381+ .collect (Collectors .toList ());
263382 }
264383}
0 commit comments