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 ;
@@ -115,6 +117,9 @@ public static class Node {
115117 private Version currentVersion ;
116118 private Process process = null ;
117119 private DistributionDescriptor distributionDescriptor ;
120+ private Set <String > extraConfigListeners = new HashSet <>();
121+ private Set <String > keystoreFileListeners = new HashSet <>();
122+ private Set <Resource > roleFileListeners = new HashSet <>();
118123
119124 public Node (Path baseWorkingDir , DistributionResolver distributionResolver , LocalNodeSpec spec ) {
120125 this (baseWorkingDir , distributionResolver , spec , null , false );
@@ -436,6 +441,10 @@ private void writeConfiguration() {
436441
437442 private void copyExtraConfigFiles () {
438443 spec .getExtraConfigFiles ().forEach ((fileName , resource ) -> {
444+ if (fileName .equals ("roles.yml" )) {
445+ throw new IllegalArgumentException ("Security roles should be configured via 'rolesFile()' method." );
446+ }
447+
439448 final Path target = configDir .resolve (fileName );
440449 final Path directory = target .getParent ();
441450 if (Files .exists (directory ) == false ) {
@@ -446,6 +455,14 @@ private void copyExtraConfigFiles() {
446455 }
447456 }
448457 resource .writeTo (target );
458+
459+ // Register and update listener for this config file
460+ if (resource instanceof MutableResource && extraConfigListeners .add (fileName )) {
461+ ((MutableResource ) resource ).addUpdateListener (updated -> {
462+ LOGGER .info ("Updating config file '{}'" , fileName );
463+ updated .writeTo (target );
464+ });
465+ }
449466 });
450467 }
451468
@@ -485,29 +502,39 @@ private void addKeystoreSettings() {
485502
486503 private void addKeystoreFiles () {
487504 spec .getKeystoreFiles ().forEach ((key , file ) -> {
488- try {
489- Path path = Files .createTempFile (tempDir , key , null );
490- file .writeTo (path );
491-
492- ProcessUtils .exec (
493- spec .getKeystorePassword (),
494- workingDir ,
495- OS .conditional (
496- c -> c .onWindows (() -> distributionDir .resolve ("bin" ).resolve ("elasticsearch-keystore.bat" ))
497- .onUnix (() -> distributionDir .resolve ("bin" ).resolve ("elasticsearch-keystore" ))
498- ),
499- getEnvironmentVariables (),
500- false ,
501- "add-file" ,
502- key ,
503- path .toString ()
504- ).waitFor ();
505- } catch (InterruptedException | IOException e ) {
506- throw new RuntimeException (e );
505+ addKeystoreFile (key , file );
506+ if (file instanceof MutableResource && keystoreFileListeners .add (key )) {
507+ ((MutableResource ) file ).addUpdateListener (updated -> {
508+ LOGGER .info ("Updating keystore file '{}'" , key );
509+ addKeystoreFile (key , updated );
510+ });
507511 }
508512 });
509513 }
510514
515+ private void addKeystoreFile (String key , Resource file ) {
516+ try {
517+ Path path = Files .createTempFile (tempDir , key , null );
518+ file .writeTo (path );
519+
520+ ProcessUtils .exec (
521+ spec .getKeystorePassword (),
522+ workingDir ,
523+ OS .conditional (
524+ c -> c .onWindows (() -> distributionDir .resolve ("bin" ).resolve ("elasticsearch-keystore.bat" ))
525+ .onUnix (() -> distributionDir .resolve ("bin" ).resolve ("elasticsearch-keystore" ))
526+ ),
527+ getEnvironmentVariables (),
528+ false ,
529+ "add-file" ,
530+ key ,
531+ path .toString ()
532+ ).waitFor ();
533+ } catch (InterruptedException | IOException e ) {
534+ throw new RuntimeException (e );
535+ }
536+ }
537+
511538 private void writeSecureSecretsFile () {
512539 if (spec .getKeystoreFiles ().isEmpty () == false ) {
513540 throw new IllegalStateException (
@@ -535,16 +562,20 @@ private void configureSecurity() {
535562 if (spec .isSecurityEnabled ()) {
536563 if (spec .getUsers ().isEmpty () == false ) {
537564 LOGGER .info ("Setting up roles.yml for node '{}'" , name );
538-
539- Path destination = workingDir .resolve ("config" ).resolve ("roles.yml" );
540- spec .getRolesFiles ().forEach (rolesFile -> {
541- try (
542- Writer writer = Files .newBufferedWriter (destination , StandardOpenOption .APPEND );
543- Reader reader = new BufferedReader (new InputStreamReader (rolesFile .asStream ()))
544- ) {
545- reader .transferTo (writer );
546- } catch (IOException e ) {
547- throw new UncheckedIOException ("Failed to append roles file " + rolesFile + " to " + destination , e );
565+ writeRolesFile ();
566+ spec .getRolesFiles ().forEach (resource -> {
567+ if (resource instanceof MutableResource && roleFileListeners .add (resource )) {
568+ ((MutableResource ) resource ).addUpdateListener (updated -> {
569+ LOGGER .info ("Updating roles.yml for node '{}'" , name );
570+ Path rolesFile = workingDir .resolve ("config" ).resolve ("roles.yml" );
571+ try {
572+ Files .delete (rolesFile );
573+ Files .copy (distributionDir .resolve ("config" ).resolve ("roles.yml" ), rolesFile );
574+ writeRolesFile ();
575+ } catch (IOException e ) {
576+ throw new UncheckedIOException (e );
577+ }
578+ });
548579 }
549580 });
550581 }
@@ -596,6 +627,20 @@ private void configureSecurity() {
596627 }
597628 }
598629
630+ private void writeRolesFile () {
631+ Path destination = workingDir .resolve ("config" ).resolve ("roles.yml" );
632+ spec .getRolesFiles ().forEach (rolesFile -> {
633+ try (
634+ Writer writer = Files .newBufferedWriter (destination , StandardOpenOption .APPEND );
635+ Reader reader = new BufferedReader (new InputStreamReader (rolesFile .asStream ()))
636+ ) {
637+ reader .transferTo (writer );
638+ } catch (IOException e ) {
639+ throw new UncheckedIOException ("Failed to append roles file " + rolesFile + " to " + destination , e );
640+ }
641+ });
642+ }
643+
599644 private void installPlugins () {
600645 if (spec .getPlugins ().isEmpty () == false ) {
601646 Pattern pattern = Pattern .compile ("(.+)(?:-\\ d+\\ .\\ d+\\ .\\ d+(-SNAPSHOT)?\\ .zip)" );
0 commit comments