org.creekservice.api.kafka.extension.config.SystemEnvPropertyOverrides Maven / Gradle / Ivy
/*
* Copyright 2022-2023 Creek Contributors (https://github.com/creek-service)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.creekservice.api.kafka.extension.config;
import static org.creekservice.api.kafka.extension.config.ClustersProperties.propertiesBuilder;
import static org.creekservice.internal.kafka.extension.config.SystemEnvProperties.prefix;
import static org.creekservice.internal.kafka.extension.config.SystemEnvProperties.propertyName;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.creekservice.api.base.annotation.VisibleForTesting;
import org.creekservice.internal.kafka.extension.config.SystemEnvProperties;
/**
* Loads Kafka client property overrides from environment variables.
*
* Variables can either target a specific cluster name or be common across all clusters.
*
*
Common variables, not targeted at a specific cluster, or where the service only accesses a
* single cluster (which is the most common pattern), should have variable names in the format:
* {@code KAFKA_<PROPERTY_NAME>} where:
*
*
* - PROPERTY_NAME is Kafka client property name, in uppercase, with periods {@code .}
* replaced with underscores {@code _}
*
*
* For example, config {@code boostrap.servers} can be set with a variable name of {@code
* KAFKA_BOOTSTRAP_SERVERS}.
*
*
In the unusual situation that a service access multiple Kafka clusters, a variable can target
* at a specific Kafka cluster. Such variables should have a name in the format: {@code
* KAFKA_<CLUSTER_NAME>_<PROPERTY_NAME>} where:
*
*
* - CLUSTER_NAME is the specific name of the Kafka cluster, as returned by {@link
* org.creekservice.api.kafka.metadata.KafkaTopicDescriptor#cluster()}), in uppercase and any
* dashes {@code -} replaced with underscores {@code _}.
*
- PROPERTY_NAME is Kafka client property name, in uppercase, with periods {@code .}
* replaced with underscores {@code _}
*
*
* For example, config {@code boostrap.servers} for the {@code main-cluster} cluster can be set
* with a variable name of {@code KAFKA_MAIN_CLUSTER_BOOTSTRAP_SERVERS}.
*/
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public final class SystemEnvPropertyOverrides implements KafkaPropertyOverrides {
private final Map env;
private Optional props = Optional.empty();
/**
* Factory method
*
* @return kafka overrides.
*/
public static KafkaPropertyOverrides systemEnvPropertyOverrides() {
return new SystemEnvPropertyOverrides(System.getenv());
}
@VisibleForTesting
SystemEnvPropertyOverrides(final Map env) {
this.env =
env.entrySet().stream()
.filter(e -> e.getKey().startsWith(SystemEnvProperties.KAFKA_PREFIX))
.collect(
Collectors.toUnmodifiableMap(
Map.Entry::getKey, Map.Entry::getValue));
}
@Override
public void init(final Set clusterNames) {
final Set specificPrefixes =
clusterNames.stream()
.map(SystemEnvProperties::prefix)
.collect(Collectors.toUnmodifiableSet());
final ClustersProperties.Builder properties = propertiesBuilder();
extractCommon(specificPrefixes, properties);
clusterNames.forEach(name -> extractSpecific(name, specificPrefixes, properties));
props = Optional.of(properties.build(clusterNames));
}
@Override
public Map get(final String clusterName) {
return props.orElseThrow(() -> new IllegalStateException("init not called"))
.get(clusterName);
}
@Override
public boolean equals(final Object o) {
return o != null && getClass() == o.getClass();
}
@Override
public int hashCode() {
return Objects.hash(getClass());
}
private void extractCommon(
final Set excludedPrefixes, final ClustersProperties.Builder properties) {
env.entrySet().stream()
.filter(e -> notExcludedPrefix(e.getKey(), excludedPrefixes))
.forEach(
e ->
propertyName(e.getKey(), "")
.ifPresent(
propName ->
properties.putCommon(
propName, e.getValue())));
}
private void extractSpecific(
final String clusterName,
final Set specificPrefixes,
final ClustersProperties.Builder properties) {
final String requiredPrefix = prefix(clusterName);
final Set excludedPrefixes = excludedPrefixes(specificPrefixes, requiredPrefix);
env.entrySet().stream()
.filter(e -> e.getKey().startsWith(requiredPrefix))
.filter(e -> notExcludedPrefix(e.getKey(), excludedPrefixes))
.forEach(
e ->
propertyName(e.getKey(), clusterName)
.ifPresent(
propName ->
properties.put(
clusterName,
propName,
e.getValue())));
}
private static Set excludedPrefixes(
final Set specificPrefixes, final String allowedPrefix) {
final Set excludedPrefixes = new HashSet<>(specificPrefixes);
excludedPrefixes.removeIf(allowedPrefix::startsWith);
return Set.copyOf(excludedPrefixes);
}
private static boolean notExcludedPrefix(final String key, final Set prefixes) {
return prefixes.stream().noneMatch(key::startsWith);
}
}