Skip to content

Commit e47e23f

Browse files
committed
wip
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
1 parent 8d6c03e commit e47e23f

File tree

8 files changed

+652
-15
lines changed

8 files changed

+652
-15
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright Java Operator SDK Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.javaoperatorsdk.operator.api.config.loader;
17+
18+
import java.util.function.BiConsumer;
19+
20+
/**
21+
* Associates a configuration key and its expected type with the setter that should be called on an
22+
* overrider when the {@link ConfigProvider} returns a value for that key.
23+
*
24+
* @param <O> the overrider type (e.g. {@code ConfigurationServiceOverrider})
25+
* @param <T> the value type expected for this key
26+
*/
27+
public class ConfigBinding<O, T> {
28+
29+
private final String key;
30+
private final Class<T> type;
31+
private final BiConsumer<O, T> setter;
32+
33+
public ConfigBinding(String key, Class<T> type, BiConsumer<O, T> setter) {
34+
this.key = key;
35+
this.type = type;
36+
this.setter = setter;
37+
}
38+
39+
public String key() {
40+
return key;
41+
}
42+
43+
public Class<T> type() {
44+
return type;
45+
}
46+
47+
public BiConsumer<O, T> setter() {
48+
return setter;
49+
}
50+
}
Lines changed: 171 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
1+
/*
2+
* Copyright Java Operator SDK Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
116
package io.javaoperatorsdk.operator.api.config.loader;
217

18+
import java.time.Duration;
19+
import java.util.List;
320
import java.util.function.Consumer;
421

522
import io.fabric8.kubernetes.api.model.HasMetadata;
@@ -8,22 +25,172 @@
825

926
public class ConfigLoader {
1027

11-
private ConfigProvider configProvider;
28+
public static final ConfigLoader DEFAULT = new ConfigLoader();
29+
30+
/**
31+
* Key prefix for operator-level (ConfigurationService) properties, e.g. {@code
32+
* josdk.concurrent.reconciliation.threads}.
33+
*/
34+
public static final String OPERATOR_KEY_PREFIX = "josdk.";
35+
36+
/**
37+
* Key prefix for controller-level properties. The controller name is inserted between this prefix
38+
* and the property name, e.g. {@code josdk.controller.my-controller.finalizer}.
39+
*/
40+
public static final String CONTROLLER_KEY_PREFIX = "josdk.controller.";
41+
42+
// ---------------------------------------------------------------------------
43+
// Operator-level (ConfigurationServiceOverrider) bindings
44+
// Only scalar / value types that a key-value ConfigProvider can supply are
45+
// included. Complex objects (KubernetesClient, ExecutorService, …) must be
46+
// configured programmatically and are intentionally omitted.
47+
// ---------------------------------------------------------------------------
48+
private static final List<ConfigBinding<ConfigurationServiceOverrider, ?>> OPERATOR_BINDINGS =
49+
List.of(
50+
new ConfigBinding<>(
51+
OPERATOR_KEY_PREFIX + "check.crd.and.validate.local.model",
52+
Boolean.class,
53+
ConfigurationServiceOverrider::checkingCRDAndValidateLocalModel),
54+
new ConfigBinding<>(
55+
OPERATOR_KEY_PREFIX + "concurrent.reconciliation.threads",
56+
Integer.class,
57+
ConfigurationServiceOverrider::withConcurrentReconciliationThreads),
58+
new ConfigBinding<>(
59+
OPERATOR_KEY_PREFIX + "concurrent.workflow.executor.threads",
60+
Integer.class,
61+
ConfigurationServiceOverrider::withConcurrentWorkflowExecutorThreads),
62+
new ConfigBinding<>(
63+
OPERATOR_KEY_PREFIX + "close.client.on.stop",
64+
Boolean.class,
65+
ConfigurationServiceOverrider::withCloseClientOnStop),
66+
new ConfigBinding<>(
67+
OPERATOR_KEY_PREFIX + "stop.on.informer.error.during.startup",
68+
Boolean.class,
69+
ConfigurationServiceOverrider::withStopOnInformerErrorDuringStartup),
70+
new ConfigBinding<>(
71+
OPERATOR_KEY_PREFIX + "cache.sync.timeout",
72+
Duration.class,
73+
ConfigurationServiceOverrider::withCacheSyncTimeout),
74+
new ConfigBinding<>(
75+
OPERATOR_KEY_PREFIX + "reconciliation.termination.timeout",
76+
Duration.class,
77+
ConfigurationServiceOverrider::withReconciliationTerminationTimeout),
78+
new ConfigBinding<>(
79+
OPERATOR_KEY_PREFIX + "ssa.based.create.update.match.for.dependent.resources",
80+
Boolean.class,
81+
ConfigurationServiceOverrider::withSSABasedCreateUpdateMatchForDependentResources),
82+
new ConfigBinding<>(
83+
OPERATOR_KEY_PREFIX + "use.ssa.to.patch.primary.resource",
84+
Boolean.class,
85+
ConfigurationServiceOverrider::withUseSSAToPatchPrimaryResource),
86+
new ConfigBinding<>(
87+
OPERATOR_KEY_PREFIX + "clone.secondary.resources.when.getting.from.cache",
88+
Boolean.class,
89+
ConfigurationServiceOverrider::withCloneSecondaryResourcesWhenGettingFromCache));
90+
91+
// ---------------------------------------------------------------------------
92+
// Controller-level (ControllerConfigurationOverrider) bindings
93+
// The key used at runtime is built as:
94+
// CONTROLLER_KEY_PREFIX + controllerName + "." + <suffix>
95+
// ---------------------------------------------------------------------------
96+
private static final List<ConfigBinding<ControllerConfigurationOverrider<?>, ?>>
97+
CONTROLLER_BINDINGS =
98+
List.of(
99+
new ConfigBinding<>(
100+
"finalizer", String.class, ControllerConfigurationOverrider::withFinalizer),
101+
new ConfigBinding<>(
102+
"generation.aware",
103+
Boolean.class,
104+
ControllerConfigurationOverrider::withGenerationAware),
105+
new ConfigBinding<>(
106+
"label.selector",
107+
String.class,
108+
ControllerConfigurationOverrider::withLabelSelector),
109+
new ConfigBinding<>(
110+
"reconciliation.max.interval",
111+
Duration.class,
112+
ControllerConfigurationOverrider::withReconciliationMaxInterval),
113+
new ConfigBinding<>(
114+
"field.manager",
115+
String.class,
116+
ControllerConfigurationOverrider::withFieldManager),
117+
new ConfigBinding<>(
118+
"trigger.reconciler.on.all.events",
119+
Boolean.class,
120+
ControllerConfigurationOverrider::withTriggerReconcilerOnAllEvents),
121+
new ConfigBinding<>(
122+
"informer.list.limit",
123+
Long.class,
124+
ControllerConfigurationOverrider::withInformerListLimit));
125+
126+
private final ConfigProvider configProvider;
12127

13128
public ConfigLoader() {
14-
this(new DefatulConfigProvider());
129+
this(new DefaultConfigProvider());
15130
}
16131

17132
public ConfigLoader(ConfigProvider configProvider) {
18133
this.configProvider = configProvider;
19134
}
20135

136+
/**
137+
* Returns a {@link Consumer} that applies every operator-level property found in the {@link
138+
* ConfigProvider} to the given {@link ConfigurationServiceOverrider}. Returns {@code null} when
139+
* no binding has a matching value, preserving the previous behaviour.
140+
*/
21141
public Consumer<ConfigurationServiceOverrider> applyConfigs() {
22-
return null;
142+
return buildConsumer(OPERATOR_BINDINGS, null);
23143
}
24144

145+
/**
146+
* Returns a {@link Consumer} that applies every controller-level property found in the {@link
147+
* ConfigProvider} to the given {@link ControllerConfigurationOverrider}. The keys are looked up
148+
* as {@code josdk.controller.<controllerName>.<property>}. Returns {@code null} when no binding
149+
* has a matching value.
150+
*/
151+
@SuppressWarnings("unchecked")
25152
public <R extends HasMetadata>
26153
Consumer<ControllerConfigurationOverrider<R>> applyControllerConfigs(String controllerName) {
27-
return null;
154+
String prefix = CONTROLLER_KEY_PREFIX + controllerName + ".";
155+
// Cast is safe: the setter BiConsumer<ControllerConfigurationOverrider<?>, T> is covariant in
156+
// its first parameter for our usage – we only ever call it with
157+
// ControllerConfigurationOverrider<R>.
158+
List<ConfigBinding<ControllerConfigurationOverrider<R>, ?>> bindings =
159+
(List<ConfigBinding<ControllerConfigurationOverrider<R>, ?>>) (List<?>) CONTROLLER_BINDINGS;
160+
return buildConsumer(bindings, prefix);
161+
}
162+
163+
/**
164+
* Iterates {@code bindings} and, for each one whose key (optionally prefixed by {@code
165+
* keyPrefix}) is present in the {@link ConfigProvider}, accumulates a call to the binding's
166+
* setter.
167+
*
168+
* @param bindings the predefined bindings to check
169+
* @param keyPrefix when non-null the key stored in the binding is treated as a suffix and this
170+
* prefix is prepended before the lookup
171+
* @return a consumer that applies all found values, or {@code null} if none were found
172+
*/
173+
private <O> Consumer<O> buildConsumer(List<ConfigBinding<O, ?>> bindings, String keyPrefix) {
174+
Consumer<O> consumer = null;
175+
for (var binding : bindings) {
176+
String lookupKey = keyPrefix == null ? binding.key() : keyPrefix + binding.key();
177+
Consumer<O> step = resolveStep(binding, lookupKey);
178+
if (step != null) {
179+
consumer = consumer == null ? step : consumer.andThen(step);
180+
}
181+
}
182+
return consumer;
183+
}
184+
185+
/**
186+
* Queries the {@link ConfigProvider} for {@code key} with the binding's type. If a value is
187+
* present, returns a {@link Consumer} that calls the binding's setter; otherwise returns {@code
188+
* null}.
189+
*/
190+
private <O, T> Consumer<O> resolveStep(ConfigBinding<O, T> binding, String key) {
191+
return configProvider
192+
.getValue(key, binding.type())
193+
.map(value -> (Consumer<O>) overrider -> binding.setter().accept(overrider, value))
194+
.orElse(null);
28195
}
29196
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,33 @@
1+
/*
2+
* Copyright Java Operator SDK Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
116
package io.javaoperatorsdk.operator.api.config.loader;
217

318
import java.util.Optional;
419

520
public interface ConfigProvider {
621

22+
/**
23+
* Returns the value associated with {@code key}, converted to {@code type}, or an empty {@link
24+
* Optional} if the key is not set.
25+
*
26+
* @param key the dot-separated configuration key, e.g. {@code josdk.cache.sync.timeout}
27+
* @param type the expected type of the value; supported types depend on the implementation
28+
* @param <T> the value type
29+
* @return an {@link Optional} containing the typed value, or empty if the key is absent
30+
* @throws IllegalArgumentException if {@code type} is not supported by the implementation
31+
*/
732
<T> Optional<T> getValue(String key, Class<T> type);
833
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/loader/DefatulConfigProvider.java

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright Java Operator SDK Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.javaoperatorsdk.operator.api.config.loader;
17+
18+
import java.time.Duration;
19+
import java.util.Optional;
20+
21+
public class DefaultConfigProvider implements ConfigProvider {
22+
23+
/**
24+
* Looks up {@code key} first as an environment variable (dots and hyphens replaced by
25+
* underscores, uppercased, e.g. {@code josdk.cache.sync.timeout} → {@code
26+
* JOSDK_CACHE_SYNC_TIMEOUT}), then as a system property with the key as-is. The environment
27+
* variable takes precedence when both are set.
28+
*/
29+
@Override
30+
@SuppressWarnings("unchecked")
31+
public <T> Optional<T> getValue(String key, Class<T> type) {
32+
String raw = resolveRaw(key);
33+
if (raw == null) {
34+
return Optional.empty();
35+
}
36+
return Optional.of(type.cast(convert(raw, type)));
37+
}
38+
39+
private String resolveRaw(String key) {
40+
String envKey = key.replace('.', '_').replace('-', '_').toUpperCase();
41+
String envValue = System.getenv(envKey);
42+
if (envValue != null) {
43+
return envValue;
44+
}
45+
return System.getProperty(key);
46+
}
47+
48+
private Object convert(String raw, Class<?> type) {
49+
if (type == String.class) {
50+
return raw;
51+
} else if (type == Boolean.class) {
52+
return Boolean.parseBoolean(raw);
53+
} else if (type == Integer.class) {
54+
return Integer.parseInt(raw);
55+
} else if (type == Long.class) {
56+
return Long.parseLong(raw);
57+
} else if (type == Duration.class) {
58+
return Duration.parse(raw);
59+
}
60+
throw new IllegalArgumentException("Unsupported config type: " + type.getName());
61+
}
62+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright Java Operator SDK Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.javaoperatorsdk.operator.api.config.loader;
17+
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
21+
import org.junit.jupiter.api.Test;
22+
23+
import static org.assertj.core.api.Assertions.assertThat;
24+
25+
class ConfigBindingTest {
26+
27+
@Test
28+
void storesKeyTypeAndSetter() {
29+
List<String> calls = new ArrayList<>();
30+
ConfigBinding<List<String>, String> binding =
31+
new ConfigBinding<>("my.key", String.class, (list, v) -> list.add(v));
32+
33+
assertThat(binding.key()).isEqualTo("my.key");
34+
assertThat(binding.type()).isEqualTo(String.class);
35+
36+
binding.setter().accept(calls, "hello");
37+
assertThat(calls).containsExactly("hello");
38+
}
39+
}

0 commit comments

Comments
 (0)