2626import org .elasticsearch .test .cluster .util .ProcessUtils ;
2727import org .elasticsearch .test .cluster .util .Retry ;
2828import org .elasticsearch .test .cluster .util .Version ;
29+ import org .elasticsearch .test .cluster .util .resource .MutableResource ;
30+ import org .elasticsearch .test .cluster .util .resource .Resource ;
2931
3032import java .io .BufferedInputStream ;
3133import java .io .BufferedReader ;
@@ -114,6 +116,9 @@ public static class Node {
114116 private Version currentVersion ;
115117 private Process process = null ;
116118 private DistributionDescriptor distributionDescriptor ;
119+ private Set <String > extraConfigListeners = new HashSet <>();
120+ private Set <String > keystoreFileListeners = new HashSet <>();
121+ private Set <Resource > roleFileListeners = new HashSet <>();
117122
118123 public Node (Path baseWorkingDir , DistributionResolver distributionResolver , LocalNodeSpec spec ) {
119124 this (baseWorkingDir , distributionResolver , spec , null , false );
@@ -435,6 +440,10 @@ private void writeConfiguration() {
435440
436441 private void copyExtraConfigFiles () {
437442 spec .getExtraConfigFiles ().forEach ((fileName , resource ) -> {
443+ if (fileName .equals ("roles.yml" )) {
444+ throw new IllegalArgumentException ("Security roles should be configured via 'rolesFile()' method." );
445+ }
446+
438447 final Path target = configDir .resolve (fileName );
439448 final Path directory = target .getParent ();
440449 if (Files .exists (directory ) == false ) {
@@ -445,6 +454,14 @@ private void copyExtraConfigFiles() {
445454 }
446455 }
447456 resource .writeTo (target );
457+
458+ // Register and update listener for this config file
459+ if (resource instanceof MutableResource && extraConfigListeners .add (fileName )) {
460+ ((MutableResource ) resource ).addUpdateListener (updated -> {
461+ LOGGER .info ("Updating config file '{}'" , fileName );
462+ updated .writeTo (target );
463+ });
464+ }
448465 });
449466 }
450467
@@ -483,29 +500,39 @@ private void addKeystoreSettings() {
483500
484501 private void addKeystoreFiles () {
485502 spec .getKeystoreFiles ().forEach ((key , file ) -> {
486- try {
487- Path path = Files .createTempFile (tempDir , key , null );
488- file .writeTo (path );
489-
490- ProcessUtils .exec (
491- spec .getKeystorePassword (),
492- workingDir ,
493- OS .conditional (
494- c -> c .onWindows (() -> distributionDir .resolve ("bin" ).resolve ("elasticsearch-keystore.bat" ))
495- .onUnix (() -> distributionDir .resolve ("bin" ).resolve ("elasticsearch-keystore" ))
496- ),
497- getEnvironmentVariables (),
498- false ,
499- "add-file" ,
500- key ,
501- path .toString ()
502- ).waitFor ();
503- } catch (InterruptedException | IOException e ) {
504- throw new RuntimeException (e );
503+ addKeystoreFile (key , file );
504+ if (file instanceof MutableResource && keystoreFileListeners .add (key )) {
505+ ((MutableResource ) file ).addUpdateListener (updated -> {
506+ LOGGER .info ("Updating keystore file '{}'" , key );
507+ addKeystoreFile (key , updated );
508+ });
505509 }
506510 });
507511 }
508512
513+ private void addKeystoreFile (String key , Resource file ) {
514+ try {
515+ Path path = Files .createTempFile (tempDir , key , null );
516+ file .writeTo (path );
517+
518+ ProcessUtils .exec (
519+ spec .getKeystorePassword (),
520+ workingDir ,
521+ OS .conditional (
522+ c -> c .onWindows (() -> distributionDir .resolve ("bin" ).resolve ("elasticsearch-keystore.bat" ))
523+ .onUnix (() -> distributionDir .resolve ("bin" ).resolve ("elasticsearch-keystore" ))
524+ ),
525+ getEnvironmentVariables (),
526+ false ,
527+ "add-file" ,
528+ key ,
529+ path .toString ()
530+ ).waitFor ();
531+ } catch (InterruptedException | IOException e ) {
532+ throw new RuntimeException (e );
533+ }
534+ }
535+
509536 private void writeSecureSecretsFile () {
510537 if (spec .getKeystoreFiles ().isEmpty () == false ) {
511538 throw new IllegalStateException (
@@ -533,16 +560,20 @@ private void configureSecurity() {
533560 if (spec .isSecurityEnabled ()) {
534561 if (spec .getUsers ().isEmpty () == false ) {
535562 LOGGER .info ("Setting up roles.yml for node '{}'" , name );
536-
537- Path destination = workingDir .resolve ("config" ).resolve ("roles.yml" );
538- spec .getRolesFiles ().forEach (rolesFile -> {
539- try (
540- Writer writer = Files .newBufferedWriter (destination , StandardOpenOption .APPEND );
541- Reader reader = new BufferedReader (new InputStreamReader (rolesFile .asStream ()))
542- ) {
543- reader .transferTo (writer );
544- } catch (IOException e ) {
545- throw new UncheckedIOException ("Failed to append roles file " + rolesFile + " to " + destination , e );
563+ writeRolesFile ();
564+ spec .getRolesFiles ().forEach (resource -> {
565+ if (resource instanceof MutableResource && roleFileListeners .add (resource )) {
566+ ((MutableResource ) resource ).addUpdateListener (updated -> {
567+ LOGGER .info ("Updating roles.yml for node '{}'" , name );
568+ Path rolesFile = workingDir .resolve ("config" ).resolve ("roles.yml" );
569+ try {
570+ Files .delete (rolesFile );
571+ Files .copy (distributionDir .resolve ("config" ).resolve ("roles.yml" ), rolesFile );
572+ writeRolesFile ();
573+ } catch (IOException e ) {
574+ throw new UncheckedIOException (e );
575+ }
576+ });
546577 }
547578 });
548579 }
@@ -594,6 +625,20 @@ private void configureSecurity() {
594625 }
595626 }
596627
628+ private void writeRolesFile () {
629+ Path destination = workingDir .resolve ("config" ).resolve ("roles.yml" );
630+ spec .getRolesFiles ().forEach (rolesFile -> {
631+ try (
632+ Writer writer = Files .newBufferedWriter (destination , StandardOpenOption .APPEND );
633+ Reader reader = new BufferedReader (new InputStreamReader (rolesFile .asStream ()))
634+ ) {
635+ reader .transferTo (writer );
636+ } catch (IOException e ) {
637+ throw new UncheckedIOException ("Failed to append roles file " + rolesFile + " to " + destination , e );
638+ }
639+ });
640+ }
641+
597642 private void installPlugins () {
598643 if (spec .getPlugins ().isEmpty () == false ) {
599644 Pattern pattern = Pattern .compile ("(.+)(?:-\\ d+\\ .\\ d+\\ .\\ d+(-SNAPSHOT)?\\ .zip)" );
0 commit comments