diff --git a/src/main/java/eu/openanalytics/shinyproxy/ShinyProxySpecExternalProvider.java b/src/main/java/eu/openanalytics/shinyproxy/ShinyProxySpecExternalProvider.java
new file mode 100644
index 00000000..68d22fb0
--- /dev/null
+++ b/src/main/java/eu/openanalytics/shinyproxy/ShinyProxySpecExternalProvider.java
@@ -0,0 +1,115 @@
+/**
+ * ShinyProxy
+ *
+ * Copyright (C) 2016-2018 Open Analytics
+ *
+ * ===========================================================================
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Apache License as published by
+ * The Apache Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Apache License for more details.
+ *
+ * You should have received a copy of the Apache License
+ * along with this program. If not, see
+ */
+package eu.openanalytics.shinyproxy;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.core.env.Environment;
+import org.springframework.stereotype.Component;
+import org.yaml.snakeyaml.Yaml;
+import org.yaml.snakeyaml.constructor.Constructor;
+
+import eu.openanalytics.containerproxy.model.spec.ProxySpec;
+import eu.openanalytics.shinyproxy.ShinyProxySpecMainProvider.ShinyProxySpec;
+
+/**
+ * This component converts proxy specs from the external specs path.
+ */
+@Component
+public class ShinyProxySpecExternalProvider {
+
+ protected final Logger log = LogManager.getLogger(getClass());
+
+ @Inject
+ private Environment environment;
+
+ private String specsExternalPath = null;
+
+ @PostConstruct
+ public void init() {
+ specsExternalPath = environment.getProperty("proxy.specs-external-path");
+ }
+
+ public List getSpecs() {
+ // No setting, we leave with empty collection
+ if (specsExternalPath == null) {
+ return Collections.emptyList();
+ }
+
+ final File appSettingsFolder = new File(specsExternalPath);
+ if (appSettingsFolder.exists() == false) {
+ return Collections.emptyList();
+ }
+
+ // We load the settings file
+ final File[] allSettingsFiles = appSettingsFolder.listFiles(new FileFilter() {
+
+ @Override
+ public boolean accept(File pathname) {
+ return pathname.getName().endsWith("yml") || pathname.getName().endsWith("yaml");
+ }
+ });
+
+ // For each file, we create a new ProxySpec
+ final List allExternalSpecs = new ArrayList<>();
+ for (int i = 0; i < allSettingsFiles.length; i++) {
+ try {
+ allExternalSpecs.add(loadYamlFile(allSettingsFiles[i]));
+ } catch (Exception e) {
+ log.error("An error occured while trying to open " + allSettingsFiles[i].getName() + " "
+ + e.getMessage());
+ }
+ }
+
+ return allExternalSpecs;
+ }
+
+ /**
+ * Map the yaml parameter with the shiny app object
+ *
+ * @param inputFile
+ * @return
+ * @throws IOException
+ */
+ private ProxySpec loadYamlFile(final File inputFile) throws IOException {
+ try (final FileReader fileReader = new FileReader(inputFile)) {
+ final Yaml yaml = new Yaml(new Constructor(ShinyProxySpec.class));
+ final ShinyProxySpec shinyProxySpec = yaml.load(fileReader);
+ log.debug(" Id " + shinyProxySpec.getId());
+ log.debug(" DisplayName " + shinyProxySpec.getDisplayName());
+ log.debug(" Description " + shinyProxySpec.getDescription());
+ log.debug(" LogoURL " + shinyProxySpec.getLogoURL());
+ // We reuse the Main provider converter
+ return ShinyProxySpecMainProvider.convert(shinyProxySpec);
+ }
+ }
+}
diff --git a/src/main/java/eu/openanalytics/shinyproxy/ShinyProxySpecMainProvider.java b/src/main/java/eu/openanalytics/shinyproxy/ShinyProxySpecMainProvider.java
new file mode 100644
index 00000000..469278fc
--- /dev/null
+++ b/src/main/java/eu/openanalytics/shinyproxy/ShinyProxySpecMainProvider.java
@@ -0,0 +1,254 @@
+/**
+ * ShinyProxy
+ *
+ * Copyright (C) 2016-2018 Open Analytics
+ *
+ * ===========================================================================
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Apache License as published by
+ * The Apache Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Apache License for more details.
+ *
+ * You should have received a copy of the Apache License
+ * along with this program. If not, see
+ */
+package eu.openanalytics.shinyproxy;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import eu.openanalytics.containerproxy.model.spec.ContainerSpec;
+import eu.openanalytics.containerproxy.model.spec.ProxyAccessControl;
+import eu.openanalytics.containerproxy.model.spec.ProxySpec;
+
+/**
+ * This component converts proxy specs from the 'ShinyProxy notation' into the
+ * 'ContainerProxy' notation. ShinyProxy notation is slightly more compact, and
+ * omits several things that Shiny apps do not need, such as definition of
+ * multiple containers.
+ *
+ * Also, if no port is specified, a port mapping is automatically created for
+ * Shiny port 3838.
+ */
+@Component
+@ConfigurationProperties(prefix = "proxy")
+public class ShinyProxySpecMainProvider {
+
+ private List specs = new ArrayList<>();
+
+ public List getSpecs() {
+ return new ArrayList<>(specs);
+ }
+
+ public ProxySpec getSpec(String id) {
+ if (id == null || id.isEmpty())
+ return null;
+ return specs.stream().filter(s -> id.equals(s.getId())).findAny().orElse(null);
+ }
+
+ public void setSpecs(List specs) {
+ this.specs = specs.stream().map(ShinyProxySpecMainProvider::convert).collect(Collectors.toList());
+ }
+
+ public static final ProxySpec convert(ShinyProxySpec from) {
+ ProxySpec to = new ProxySpec();
+ to.setId(from.getId());
+ to.setDisplayName(from.getDisplayName());
+ to.setDescription(from.getDescription());
+ to.setLogoURL(from.getLogoURL());
+
+ if (from.getAccessGroups() != null && from.getAccessGroups().length > 0) {
+ ProxyAccessControl acl = new ProxyAccessControl();
+ acl.setGroups(from.getAccessGroups());
+ to.setAccessControl(acl);
+ }
+
+ ContainerSpec cSpec = new ContainerSpec();
+ cSpec.setImage(from.getContainerImage());
+ cSpec.setCmd(from.getContainerCmd());
+ cSpec.setEnv(from.getContainerEnv());
+ cSpec.setEnvFile(from.getContainerEnvFile());
+ cSpec.setNetwork(from.getContainerNetwork());
+ cSpec.setNetworkConnections(from.getContainerNetworkConnections());
+ cSpec.setDns(from.getContainerDns());
+ cSpec.setVolumes(from.getContainerVolumes());
+ cSpec.setMemory(from.getContainerMemory());
+ cSpec.setPrivileged(from.isContainerPrivileged());
+
+ Map portMapping = new HashMap<>();
+ if (from.getPort() > 0) {
+ portMapping.put("default", from.getPort());
+ } else {
+ portMapping.put("default", 3838);
+ }
+ cSpec.setPortMapping(portMapping);
+
+ to.setContainerSpecs(Collections.singletonList(cSpec));
+
+ return to;
+ }
+
+ public static class ShinyProxySpec {
+
+ private String id;
+ private String displayName;
+ private String description;
+ private String logoURL;
+
+ private String containerImage;
+ private String[] containerCmd;
+ private Map containerEnv;
+ private String containerEnvFile;
+ private String containerNetwork;
+ private String[] containerNetworkConnections;
+ private String[] containerDns;
+ private String[] containerVolumes;
+ private String containerMemory;
+ private boolean containerPrivileged;
+
+ private int port;
+ private String[] accessGroups;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public void setDisplayName(String displayName) {
+ this.displayName = displayName;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getLogoURL() {
+ return logoURL;
+ }
+
+ public void setLogoURL(String logoURL) {
+ this.logoURL = logoURL;
+ }
+
+ public String getContainerImage() {
+ return containerImage;
+ }
+
+ public void setContainerImage(String containerImage) {
+ this.containerImage = containerImage;
+ }
+
+ public String[] getContainerCmd() {
+ return containerCmd;
+ }
+
+ public void setContainerCmd(String[] containerCmd) {
+ this.containerCmd = containerCmd;
+ }
+
+ public Map getContainerEnv() {
+ return containerEnv;
+ }
+
+ public void setContainerEnv(Map containerEnv) {
+ this.containerEnv = containerEnv;
+ }
+
+ public String getContainerEnvFile() {
+ return containerEnvFile;
+ }
+
+ public void setContainerEnvFile(String containerEnvFile) {
+ this.containerEnvFile = containerEnvFile;
+ }
+
+ public String getContainerNetwork() {
+ return containerNetwork;
+ }
+
+ public void setContainerNetwork(String containerNetwork) {
+ this.containerNetwork = containerNetwork;
+ }
+
+ public String[] getContainerNetworkConnections() {
+ return containerNetworkConnections;
+ }
+
+ public void setContainerNetworkConnections(String[] containerNetworkConnections) {
+ this.containerNetworkConnections = containerNetworkConnections;
+ }
+
+ public String[] getContainerDns() {
+ return containerDns;
+ }
+
+ public void setContainerDns(String[] containerDns) {
+ this.containerDns = containerDns;
+ }
+
+ public String[] getContainerVolumes() {
+ return containerVolumes;
+ }
+
+ public void setContainerVolumes(String[] containerVolumes) {
+ this.containerVolumes = containerVolumes;
+ }
+
+ public String getContainerMemory() {
+ return containerMemory;
+ }
+
+ public void setContainerMemory(String containerMemory) {
+ this.containerMemory = containerMemory;
+ }
+
+ public boolean isContainerPrivileged() {
+ return containerPrivileged;
+ }
+
+ public void setContainerPrivileged(boolean containerPrivileged) {
+ this.containerPrivileged = containerPrivileged;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ public String[] getAccessGroups() {
+ return accessGroups;
+ }
+
+ public void setAccessGroups(String[] accessGroups) {
+ this.accessGroups = accessGroups;
+ }
+
+ }
+}
diff --git a/src/main/java/eu/openanalytics/shinyproxy/ShinyProxySpecProvider.java b/src/main/java/eu/openanalytics/shinyproxy/ShinyProxySpecProvider.java
index 905e5085..d6512d4b 100644
--- a/src/main/java/eu/openanalytics/shinyproxy/ShinyProxySpecProvider.java
+++ b/src/main/java/eu/openanalytics/shinyproxy/ShinyProxySpecProvider.java
@@ -22,232 +22,64 @@
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
+import java.util.Comparator;
import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-import org.springframework.boot.context.properties.ConfigurationProperties;
+import javax.inject.Inject;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
-import eu.openanalytics.containerproxy.model.spec.ContainerSpec;
-import eu.openanalytics.containerproxy.model.spec.ProxyAccessControl;
import eu.openanalytics.containerproxy.model.spec.ProxySpec;
import eu.openanalytics.containerproxy.spec.IProxySpecProvider;
/**
- * This component converts proxy specs from the 'ShinyProxy notation' into the 'ContainerProxy' notation.
- * ShinyProxy notation is slightly more compact, and omits several things that Shiny apps do not need,
- * such as definition of multiple containers.
- *
- * Also, if no port is specified, a port mapping is automatically created for Shiny port 3838.
+ * This component aggregate the proxy specs from the Main Provider (mainning the
+ * application.yml file) and the others settings files as definied in the
+ * proxy.specs-external-path properties.
*/
@Component
@Primary
-@ConfigurationProperties(prefix = "proxy")
public class ShinyProxySpecProvider implements IProxySpecProvider {
- private List specs = new ArrayList<>();
+ protected final Logger log = LogManager.getLogger(getClass());
+ @Inject
+ private ShinyProxySpecMainProvider mainProvider;
+
+ @Inject
+ private ShinyProxySpecExternalProvider externalProvider;
+
public List getSpecs() {
- return new ArrayList<>(specs);
+ final List allProxySpec = new ArrayList<>(mainProvider.getSpecs());
+ allProxySpec.addAll(externalProvider.getSpecs());
+ Collections.sort(allProxySpec, PROXY_SPEC_COMPARATOR);
+ log.debug("We have found " + allProxySpec.size() + " spec(s)");
+ return allProxySpec;
}
-
+
public ProxySpec getSpec(String id) {
- if (id == null || id.isEmpty()) return null;
- return specs.stream().filter(s -> id.equals(s.getId())).findAny().orElse(null);
- }
-
- public void setSpecs(List specs) {
- this.specs = specs.stream().map(ShinyProxySpecProvider::convert).collect(Collectors.toList());
- }
-
- private static ProxySpec convert(ShinyProxySpec from) {
- ProxySpec to = new ProxySpec();
- to.setId(from.getId());
- to.setDisplayName(from.getDisplayName());
- to.setDescription(from.getDescription());
- to.setLogoURL(from.getLogoURL());
-
- if (from.getAccessGroups() != null && from.getAccessGroups().length > 0) {
- ProxyAccessControl acl = new ProxyAccessControl();
- acl.setGroups(from.getAccessGroups());
- to.setAccessControl(acl);
- }
-
- ContainerSpec cSpec = new ContainerSpec();
- cSpec.setImage(from.getContainerImage());
- cSpec.setCmd(from.getContainerCmd());
- cSpec.setEnv(from.getContainerEnv());
- cSpec.setEnvFile(from.getContainerEnvFile());
- cSpec.setNetwork(from.getContainerNetwork());
- cSpec.setNetworkConnections(from.getContainerNetworkConnections());
- cSpec.setDns(from.getContainerDns());
- cSpec.setVolumes(from.getContainerVolumes());
- cSpec.setMemory(from.getContainerMemory());
- cSpec.setPrivileged(from.isContainerPrivileged());
-
- Map portMapping = new HashMap<>();
- if (from.getPort() > 0) {
- portMapping.put("default", from.getPort());
- } else {
- portMapping.put("default", 3838);
- }
- cSpec.setPortMapping(portMapping);
-
- to.setContainerSpecs(Collections.singletonList(cSpec));
-
- return to;
+ if (id == null || id.isEmpty())
+ return null;
+ return getSpecs().stream().filter(s -> id.equals(s.getId())).findAny().orElse(null);
}
-
- public static class ShinyProxySpec {
-
- private String id;
- private String displayName;
- private String description;
- private String logoURL;
-
- private String containerImage;
- private String[] containerCmd;
- private Map containerEnv;
- private String containerEnvFile;
- private String containerNetwork;
- private String[] containerNetworkConnections;
- private String[] containerDns;
- private String[] containerVolumes;
- private String containerMemory;
- private boolean containerPrivileged;
-
- private int port;
- private String[] accessGroups;
-
- public String getId() {
- return id;
- }
-
- public void setId(String id) {
- this.id = id;
- }
-
- public String getDisplayName() {
- return displayName;
- }
- public void setDisplayName(String displayName) {
- this.displayName = displayName;
- }
-
- public String getDescription() {
- return description;
- }
-
- public void setDescription(String description) {
- this.description = description;
- }
-
- public String getLogoURL() {
- return logoURL;
- }
-
- public void setLogoURL(String logoURL) {
- this.logoURL = logoURL;
- }
+ // This comparator uses the display name to sort
+ private static final Comparator PROXY_SPEC_COMPARATOR = new Comparator() {
- public String getContainerImage() {
- return containerImage;
+ @Override
+ public int compare(ProxySpec o1, ProxySpec o2) {
+ if (o1 == o2)
+ return 0;
+ if (o1 == null)
+ return -1;
+ if (o2 == null)
+ return 1;
+ return StringUtils.compare(o1.getDisplayName(), o2.getDisplayName());
}
+ };
- public void setContainerImage(String containerImage) {
- this.containerImage = containerImage;
- }
-
- public String[] getContainerCmd() {
- return containerCmd;
- }
-
- public void setContainerCmd(String[] containerCmd) {
- this.containerCmd = containerCmd;
- }
-
- public Map getContainerEnv() {
- return containerEnv;
- }
-
- public void setContainerEnv(Map containerEnv) {
- this.containerEnv = containerEnv;
- }
-
- public String getContainerEnvFile() {
- return containerEnvFile;
- }
-
- public void setContainerEnvFile(String containerEnvFile) {
- this.containerEnvFile = containerEnvFile;
- }
-
- public String getContainerNetwork() {
- return containerNetwork;
- }
-
- public void setContainerNetwork(String containerNetwork) {
- this.containerNetwork = containerNetwork;
- }
-
- public String[] getContainerNetworkConnections() {
- return containerNetworkConnections;
- }
-
- public void setContainerNetworkConnections(String[] containerNetworkConnections) {
- this.containerNetworkConnections = containerNetworkConnections;
- }
-
- public String[] getContainerDns() {
- return containerDns;
- }
-
- public void setContainerDns(String[] containerDns) {
- this.containerDns = containerDns;
- }
-
- public String[] getContainerVolumes() {
- return containerVolumes;
- }
-
- public void setContainerVolumes(String[] containerVolumes) {
- this.containerVolumes = containerVolumes;
- }
-
- public String getContainerMemory() {
- return containerMemory;
- }
-
- public void setContainerMemory(String containerMemory) {
- this.containerMemory = containerMemory;
- }
-
- public boolean isContainerPrivileged() {
- return containerPrivileged;
- }
-
- public void setContainerPrivileged(boolean containerPrivileged) {
- this.containerPrivileged = containerPrivileged;
- }
-
- public int getPort() {
- return port;
- }
-
- public void setPort(int port) {
- this.port = port;
- }
-
- public String[] getAccessGroups() {
- return accessGroups;
- }
-
- public void setAccessGroups(String[] accessGroups) {
- this.accessGroups = accessGroups;
- }
- }
}