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