forked from quarkusio/quarkus
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for reading configuration from a ConfigMap
Fixes: quarkusio#6745
- Loading branch information
Showing
13 changed files
with
570 additions
and
2 deletions.
There are no files selected for viewing
20 changes: 20 additions & 0 deletions
20
...ment/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesConfigProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package io.quarkus.kubernetes.client.deployment; | ||
|
||
import io.quarkus.deployment.annotations.BuildStep; | ||
import io.quarkus.deployment.annotations.ExecutionTime; | ||
import io.quarkus.deployment.annotations.Record; | ||
import io.quarkus.deployment.builditem.RunTimeConfigurationSourceValueBuildItem; | ||
import io.quarkus.kubernetes.client.runtime.KubernetesClientBuildConfig; | ||
import io.quarkus.kubernetes.client.runtime.KubernetesConfigRecorder; | ||
import io.quarkus.kubernetes.client.runtime.KubernetesConfigSourceConfig; | ||
|
||
public class KubernetesConfigProcessor { | ||
|
||
@BuildStep | ||
@Record(ExecutionTime.RUNTIME_INIT) | ||
public RunTimeConfigurationSourceValueBuildItem configure(KubernetesConfigRecorder recorder, | ||
KubernetesConfigSourceConfig config, KubernetesClientBuildConfig clientConfig) { | ||
return new RunTimeConfigurationSourceValueBuildItem( | ||
recorder.configMaps(config, clientConfig)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
63 changes: 63 additions & 0 deletions
63
...ime/src/main/java/io/quarkus/kubernetes/client/runtime/ConfigMapConfigSourceProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package io.quarkus.kubernetes.client.runtime; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.List; | ||
|
||
import org.eclipse.microprofile.config.spi.ConfigSource; | ||
import org.eclipse.microprofile.config.spi.ConfigSourceProvider; | ||
import org.jboss.logging.Logger; | ||
|
||
import io.fabric8.kubernetes.api.model.ConfigMap; | ||
import io.fabric8.kubernetes.client.KubernetesClient; | ||
|
||
class ConfigMapConfigSourceProvider implements ConfigSourceProvider { | ||
|
||
private static final Logger log = Logger.getLogger(ConfigMapConfigSourceProvider.class); | ||
|
||
private final KubernetesConfigSourceConfig config; | ||
private final KubernetesClient client; | ||
|
||
public ConfigMapConfigSourceProvider(KubernetesConfigSourceConfig config, KubernetesClient client) { | ||
this.config = config; | ||
this.client = client; | ||
} | ||
|
||
@Override | ||
public Iterable<ConfigSource> getConfigSources(ClassLoader forClassLoader) { | ||
if (!config.configMaps.isPresent()) { | ||
log.debug("No ConfigMaps were configured for config source lookup"); | ||
return Collections.emptyList(); | ||
} | ||
|
||
List<String> configMapNames = config.configMaps.orElse(Collections.emptyList()); | ||
List<ConfigSource> result = new ArrayList<>(configMapNames.size()); | ||
|
||
try { | ||
for (String configMapName : configMapNames) { | ||
if (log.isDebugEnabled()) { | ||
log.debug("Attempting to read ConfigMap " + configMapName); | ||
} | ||
ConfigMap configMap = client.configMaps().withName(configMapName).get(); | ||
if (configMap == null) { | ||
String message = "ConfigMap '" + configMap + "' not found in namespace '" | ||
+ client.getConfiguration().getNamespace() + "'"; | ||
if (config.failOnMissingConfig) { | ||
throw new RuntimeException(message); | ||
} else { | ||
log.info(message); | ||
} | ||
} else { | ||
result.addAll(ConfigMapUtil.toConfigSources(configMap)); | ||
if (log.isDebugEnabled()) { | ||
log.debug("Done reading ConfigMap " + configMap); | ||
} | ||
} | ||
} | ||
return result; | ||
} catch (Exception e) { | ||
throw new RuntimeException("Unable to obtain configuration for ConfigMap objects for Kubernetes API Server at: " | ||
+ client.getConfiguration().getMasterUrl(), e); | ||
} | ||
} | ||
} |
147 changes: 147 additions & 0 deletions
147
...etes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/ConfigMapUtil.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
package io.quarkus.kubernetes.client.runtime; | ||
|
||
import java.io.IOException; | ||
import java.io.StringReader; | ||
import java.io.UncheckedIOException; | ||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Properties; | ||
|
||
import org.eclipse.microprofile.config.spi.ConfigSource; | ||
import org.jboss.logging.Logger; | ||
|
||
import io.fabric8.kubernetes.api.model.ConfigMap; | ||
import io.smallrye.config.common.MapBackedConfigSource; | ||
import io.smallrye.config.source.yaml.YamlConfigSource; | ||
|
||
final class ConfigMapUtil { | ||
|
||
private static final Logger log = Logger.getLogger(ConfigMapUtil.class); | ||
|
||
private static final String APPLICATION_YML = "application.yml"; | ||
private static final String APPLICATION_YAML = "application.yaml"; | ||
private static final String APPLICATION_PROPERTIES = "application.properties"; | ||
|
||
private static final int ORDINAL = 270; // this is higher than the file system or jar ordinals, but lower than env vars | ||
|
||
private ConfigMapUtil() { | ||
} | ||
|
||
/** | ||
* Returns a list of {@code ConfigSource} for the literal data that is contained in the ConfigMap | ||
* and for the application.{properties|yaml|yml} files that might be contained in it as well | ||
* | ||
* All the {@code ConfigSource} objects use the same ordinal which is higher than the ordinal | ||
* of normal configuration files, but lower than that of environment variables | ||
*/ | ||
static List<ConfigSource> toConfigSources(ConfigMap configMap) { | ||
String configMapName = configMap.getMetadata().getName(); | ||
if (log.isDebugEnabled()) { | ||
log.debug("Attempting to convert data in ConfigMap '" + configMapName + "' to a list of ConfigSource objects"); | ||
} | ||
|
||
ConfigMapData configMapData = parse(configMap); | ||
List<ConfigSource> result = new ArrayList<>(configMapData.fileData.size() + 1); | ||
|
||
if (!configMapData.literalData.isEmpty()) { | ||
if (log.isDebugEnabled()) { | ||
log.debug("Adding a ConfigSource for the literal data of ConfigMap '" + configMapName + "'"); | ||
} | ||
result.add(new ConfigMapLiteralDataPropertiesConfigSource(configMapName + "-literalData", configMapData.literalData, | ||
ORDINAL)); | ||
} | ||
for (Map.Entry<String, String> entry : configMapData.fileData) { | ||
String fileName = entry.getKey(); | ||
String rawFileData = entry.getValue(); | ||
if (APPLICATION_PROPERTIES.equals(fileName)) { | ||
if (log.isDebugEnabled()) { | ||
log.debug("Adding a Properties ConfigSource for file '" + fileName + "' of ConfigMap '" + configMapName | ||
+ "'"); | ||
} | ||
result.add(new ConfigMapStringInputPropertiesConfigSource(configMapName, fileName, rawFileData, ORDINAL)); | ||
} else if (APPLICATION_YAML.equals(fileName) || APPLICATION_YML.equals(fileName)) { | ||
if (log.isDebugEnabled()) { | ||
log.debug("Adding a YAML ConfigSource for file '" + fileName + "' of ConfigMap '" + configMapName + "'"); | ||
} | ||
result.add(new ConfigMapStringInputYamlConfigSource(configMapName, fileName, rawFileData, ORDINAL)); | ||
} | ||
} | ||
|
||
if (log.isDebugEnabled()) { | ||
log.debug("ConfigMap's '" + configMapName + "' converted into " + result.size() + "ConfigSource objects"); | ||
} | ||
return result; | ||
} | ||
|
||
private static ConfigMapData parse(ConfigMap configMap) { | ||
Map<String, String> data = configMap.getData(); | ||
if ((data == null) || data.isEmpty()) { | ||
return new ConfigMapData(Collections.emptyMap(), Collections.emptyList()); | ||
} | ||
|
||
Map<String, String> literalData = new HashMap<>(); | ||
List<Map.Entry<String, String>> fileData = new ArrayList<>(); | ||
for (Map.Entry<String, String> entry : data.entrySet()) { | ||
String key = entry.getKey(); | ||
if (key.endsWith(".yml") || key.endsWith(".yaml") || key.endsWith(".properties")) { | ||
fileData.add(entry); | ||
} else { | ||
literalData.put(key, entry.getValue()); | ||
} | ||
} | ||
|
||
return new ConfigMapData(literalData, fileData); | ||
} | ||
|
||
private static class ConfigMapData { | ||
final Map<String, String> literalData; | ||
final List<Map.Entry<String, String>> fileData; | ||
|
||
ConfigMapData(Map<String, String> literalData, List<Map.Entry<String, String>> fileData) { | ||
this.literalData = literalData; | ||
this.fileData = fileData; | ||
} | ||
} | ||
|
||
private static final class ConfigMapLiteralDataPropertiesConfigSource extends MapBackedConfigSource { | ||
|
||
private static final String NAME_PREFIX = "ConfigMapLiteralDataPropertiesConfigSource[configMap="; | ||
|
||
public ConfigMapLiteralDataPropertiesConfigSource(String configMapName, Map<String, String> propertyMap, int ordinal) { | ||
super(NAME_PREFIX + configMapName + "]", propertyMap, ordinal); | ||
} | ||
} | ||
|
||
private static class ConfigMapStringInputPropertiesConfigSource extends MapBackedConfigSource { | ||
|
||
private static final String NAME_FORMAT = "ConfigMapStringInputPropertiesConfigSource[configMap=%s,file=%s]"; | ||
|
||
ConfigMapStringInputPropertiesConfigSource(String configMapName, String fileName, String input, int ordinal) { | ||
super(String.format(NAME_FORMAT, configMapName, fileName), readProperties(input), ordinal); | ||
} | ||
|
||
@SuppressWarnings({ "rawtypes", "unchecked" }) | ||
private static Map<String, String> readProperties(String rawData) { | ||
try (StringReader br = new StringReader(rawData)) { | ||
final Properties properties = new Properties(); | ||
properties.load(br); | ||
return (Map<String, String>) (Map) properties; | ||
} catch (IOException e) { | ||
throw new UncheckedIOException(e); | ||
} | ||
} | ||
} | ||
|
||
private static class ConfigMapStringInputYamlConfigSource extends YamlConfigSource { | ||
|
||
private static final String NAME_FORMAT = "ConfigMapStringInputYamlConfigSource[configMap=%s,file=%s]"; | ||
|
||
public ConfigMapStringInputYamlConfigSource(String configMapName, String fileName, String input, int ordinal) { | ||
super(String.format(NAME_FORMAT, configMapName, fileName), input, ordinal); | ||
} | ||
} | ||
|
||
} |
41 changes: 41 additions & 0 deletions
41
.../runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesConfigRecorder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package io.quarkus.kubernetes.client.runtime; | ||
|
||
import java.util.Collections; | ||
|
||
import org.eclipse.microprofile.config.spi.ConfigSource; | ||
import org.eclipse.microprofile.config.spi.ConfigSourceProvider; | ||
import org.jboss.logging.Logger; | ||
|
||
import io.quarkus.runtime.RuntimeValue; | ||
import io.quarkus.runtime.annotations.Recorder; | ||
|
||
@Recorder | ||
public class KubernetesConfigRecorder { | ||
|
||
private static final Logger log = Logger.getLogger(KubernetesConfigRecorder.class); | ||
|
||
public RuntimeValue<ConfigSourceProvider> configMaps(KubernetesConfigSourceConfig kubernetesConfigSourceConfig, | ||
KubernetesClientBuildConfig clientConfig) { | ||
if (!kubernetesConfigSourceConfig.enabled) { | ||
log.debug( | ||
"No attempt will be made to obtain configuration from the Kubernetes API server because the functionality has been disabled via configuration"); | ||
return emptyRuntimeValue(); | ||
} | ||
|
||
return new RuntimeValue<>(new ConfigMapConfigSourceProvider(kubernetesConfigSourceConfig, | ||
KubernetesClientUtils.createClient(clientConfig))); | ||
} | ||
|
||
private RuntimeValue<ConfigSourceProvider> emptyRuntimeValue() { | ||
return new RuntimeValue<>(new EmptyConfigSourceProvider()); | ||
} | ||
|
||
private static class EmptyConfigSourceProvider implements ConfigSourceProvider { | ||
|
||
@Override | ||
public Iterable<ConfigSource> getConfigSources(ClassLoader forClassLoader) { | ||
return Collections.emptyList(); | ||
} | ||
} | ||
|
||
} |
31 changes: 31 additions & 0 deletions
31
...time/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesConfigSourceConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package io.quarkus.kubernetes.client.runtime; | ||
|
||
import java.util.List; | ||
import java.util.Optional; | ||
|
||
import io.quarkus.runtime.annotations.ConfigItem; | ||
import io.quarkus.runtime.annotations.ConfigPhase; | ||
import io.quarkus.runtime.annotations.ConfigRoot; | ||
|
||
@ConfigRoot(name = "kubernetes-config", phase = ConfigPhase.BOOTSTRAP) | ||
public class KubernetesConfigSourceConfig { | ||
|
||
/** | ||
* If set to true, the application will attempt to look up the configuration from the API server | ||
*/ | ||
@ConfigItem(defaultValue = "false") | ||
public boolean enabled; | ||
|
||
/** | ||
* If set to true, the application will not start if any of the configured config sources cannot be located | ||
*/ | ||
@ConfigItem(defaultValue = "true") | ||
public boolean failOnMissingConfig; | ||
|
||
/** | ||
* ConfigMaps to look for in the namespace that the Kubernetes Client has been configured for | ||
*/ | ||
@ConfigItem | ||
public Optional<List<String>> configMaps; | ||
|
||
} |
Oops, something went wrong.