diff --git a/src/main/java/com/redhat/devtools/intellij/common/utils/ConfigWatcher.java b/src/main/java/com/redhat/devtools/intellij/common/utils/ConfigWatcher.java index 696f992..d03586f 100644 --- a/src/main/java/com/redhat/devtools/intellij/common/utils/ConfigWatcher.java +++ b/src/main/java/com/redhat/devtools/intellij/common/utils/ConfigWatcher.java @@ -13,7 +13,6 @@ import com.intellij.openapi.diagnostic.Logger; import io.fabric8.kubernetes.api.model.Config; import io.fabric8.kubernetes.client.internal.KubeConfigUtils; -import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.nio.file.FileSystems; @@ -24,13 +23,16 @@ import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; +import java.util.Collection; +import java.util.List; import java.util.function.Consumer; +import java.util.stream.Collectors; public class ConfigWatcher implements Runnable { private static final Logger LOG = Logger.getInstance(ConfigWatcher.class); - private final Path config; + private final List configs; protected Listener listener; public interface Listener { @@ -42,94 +44,131 @@ public ConfigWatcher(String config, Listener listener) { } public ConfigWatcher(Path config, Listener listener) { - this.config = config; + this(List.of(config), listener); + } + + public ConfigWatcher(List configs, Listener listener) { + this.configs = configs; this.listener = listener; } @Override public void run() { - runOnConfigChange((Config config) -> { - listener.onUpdate(this, config); - }); - } - - protected Config loadConfig() { - try { - return ConfigHelper.loadKubeConfig(config.toAbsolutePath().toString()); - } catch (IOException e) { - return null; - } + runOnConfigChange((Config config) -> + listener.onUpdate(this, config)); } private void runOnConfigChange(Consumer consumer) { - try (WatchService service = newWatchService()) { - registerWatchService(service); - WatchKey key; - while ((key = service.take()) != null) { - key.pollEvents().stream() - .forEach((event) -> consumer.accept(loadConfig(getPath(event)))); - key.reset(); - } - } catch (IOException | InterruptedException e) { + try (WatchService service = createWatchService()) { + Collection watchedDirectories = getWatchedDirectories(); + HighSensitivityRegistrar registrar = new HighSensitivityRegistrar(); + watchedDirectories.forEach(directory -> { + WatchServiceListener listener = new WatchServiceListener(consumer, directory, service, registrar); + listener.start(); + }); + } catch (IOException e) { + String configPaths = configs.stream() + .map(path -> path.toAbsolutePath().toString()) + .collect(Collectors.joining()); Logger.getInstance(ConfigWatcher.class).warn( - "Could not watch kubernetes config file at " + config.toAbsolutePath(), e); + "Could not watch kubernetes config file at " + configPaths, e); } } - protected WatchService newWatchService() throws IOException { + protected WatchService createWatchService() throws IOException { return FileSystems.getDefault().newWatchService(); } - @NotNull - private void registerWatchService(WatchService service) throws IOException { - HighSensitivityRegistrar modifier = new HighSensitivityRegistrar(); - modifier.registerService(getWatchedPath(), - new WatchEvent.Kind[]{ - StandardWatchEventKinds.ENTRY_CREATE, - StandardWatchEventKinds.ENTRY_MODIFY, - StandardWatchEventKinds.ENTRY_DELETE}, - service); + private Collection getWatchedDirectories() { + return configs.stream() + .map(Path::getParent) + .filter(Files::isDirectory) + .collect(Collectors.toSet()); } - protected boolean isConfigPath(Path path) { - return path.equals(config); - } + private class WatchServiceListener { + private final Path directory; + private final WatchService service; + private final HighSensitivityRegistrar registrar; + private final Consumer consumer; + + WatchServiceListener(Consumer consumer, Path directory, WatchService service, HighSensitivityRegistrar registrar) { + this.directory = directory; + this.service = service; + this.registrar = registrar; + this.consumer = consumer; + } - /** - * Returns {@link Config} for the given path if the kube config file - *
    - *
  • exists and
  • - *
  • is not empty and
  • - *
  • is valid yaml
  • - *
- * Returns {@code null} otherwise. - * - * @param path the path to the kube config - * @return returns true if the kube config that the event points to exists, is not empty and is valid yaml - */ - private Config loadConfig(Path path) { - if (path == null) { - return null; + public void start() { + try { + registerService(directory, service, registrar); + poll(consumer, service); + } catch (InterruptedException e) { + LOG.warn("Watching " + directory + "was interrupted", e); + } catch (IOException e) { + LOG.warn("Could not watch " + directory, e); + } } - try { - if (Files.exists(path) - && isConfigPath(path) - && Files.size(path) > 0) { - return KubeConfigUtils.parseConfig(path.toFile()); + + private void poll(Consumer consumer, WatchService service) throws InterruptedException { + for (WatchKey key = service.take(); key != null; key = service.take()) { + key.pollEvents().forEach((event) -> { + Path changed = getAbsolutePath(directory, (Path) event.context()); + if (isConfigPath(changed)) { + consumer.accept(loadConfig(changed)); + } + }); + key.reset(); } - } catch (Exception e) { - // only catch - LOG.warn("Could not load kube config at " + path.toAbsolutePath(), e); } - return null; - } - private Path getPath(WatchEvent event) { - return getWatchedPath().resolve((Path) event.context()); - } + private void registerService(Path path, WatchService service, HighSensitivityRegistrar registrar) throws IOException { + registrar.registerService(path, + new WatchEvent.Kind[]{ + StandardWatchEventKinds.ENTRY_CREATE, + StandardWatchEventKinds.ENTRY_MODIFY, + StandardWatchEventKinds.ENTRY_DELETE}, + service); + } + + protected boolean isConfigPath(Path path) { + return configs != null + && configs.contains(path); + } + + /** + * Returns {@link Config} for the given path if the kube config file + *
    + *
  • exists and
  • + *
  • is not empty and
  • + *
  • is valid yaml
  • + *
+ * Returns {@code null} otherwise. + * + * @param path the path to the kube config + * @return returns true if the kube config that the event points to exists, is not empty and is valid yaml + */ + private Config loadConfig(Path path) { + // TODO: replace by Config#getKubeConfigFiles once kubernetes-client 7.0 is available + if (path == null) { + return null; + } + try { + if (Files.exists(path) + && Files.size(path) > 0) { + return KubeConfigUtils.parseConfig(path.toFile()); + } + } catch (Exception e) { + // only catch + LOG.warn("Could not load kube config at " + path.toAbsolutePath(), e); + } + return null; + } + + private Path getAbsolutePath(Path directory, Path relativePath) { + return directory.resolve(relativePath); + } - private Path getWatchedPath() { - return config.getParent(); } }