-
Notifications
You must be signed in to change notification settings - Fork 221
Description
Right now, the Matcher
interface defines a single method: public boolean match(Deployment actual, Deployment desired, Context context)
. This works quite nicely for the default implementation. However, in many cases, when people actually want to implement their own optimised version, it would be more effective and efficient to get the primary resource instead of the desired state as the desired state might be quite complex (e.g. a Deployment
) and navigating to the salient parts, not easy.
Contrast the following implementations:
- Using the current interface:
public boolean match(Deployment actual, Deployment desired, Context context) {
final var maybeDesiredContainer = desired.getSpec().getTemplate().getSpec().getContainers()
.stream()
.findFirst();
if (maybeDesiredContainer.isEmpty()) {
return true;
}
final var desiredContainer = maybeDesiredContainer.get();
final var container = actual.getSpec().getTemplate().getSpec().getContainers()
.stream()
.findFirst();
return container.map(c -> desiredContainer.getImage().equals(c.getImage())
&& desiredContainer.getEnv().equals(c.getEnv()))
.orElse(false);
}
- Using a version that would pass the primary resource instead:
public boolean match(Deployment actual, ExposedApp primary, Context context) {
final var spec = primary.getSpec();
Optional<Container> container = actual.getSpec().getTemplate().getSpec().getContainers()
.stream()
.findFirst();
return container.map(
c -> spec.getImageRef().equals(c.getImage()) && (spec.getEnv() == null || spec.getEnv()
.equals(convert(c.getEnv()))))
.orElse(false);
}
private Map<String, String> convert(List<EnvVar> envVars) {
final var result = new HashMap<String, String>(envVars.size());
envVars.forEach(
envVar -> result.put(envVar.getName().toLowerCase(Locale.ROOT), envVar.getValue()));
return result;
}
I think the second implementation is clearer as to what is going on exactly and the relation to the primary resource state clearer. Granted, in this second case, we do need to convert part of the actual state to something that matches what is provided by the primary resource (that's the purpose of the convert
method) but the relation between the primary resource state and whether the dependent state matches it is more explicit in this case.
This would also have the added benefit of not having to compute the full desired state if we can determine quickly that the actual state matches the expectations set by the primary resource as AbstractDependentResource.reconcile
could be rewritten as follows:
public void reconcile(P primary, Context context) {
final var creatable = isCreatable(primary, context);
final var updatable = isUpdatable(primary, context);
if (creatable || updatable) {
var maybeActual = getResource(primary);
if (maybeActual.isEmpty()) {
if (creatable) {
var desired = desired(primary, context);
log.debug("Creating dependent {} for primary {}", desired, primary);
creator.create(desired, primary, context);
}
} else {
final var actual = maybeActual.get();
if (updatable && !updater.match(actual, primary, context)) {
var desired = desired(primary, context);
log.debug("Updating dependent {} for primary {}", desired, primary);
updater.update(actual, desired, primary, context);
} else {
log.debug("Update skipped for dependent {} as it matched the existing one", actual);
}
}
} else {
log.debug(
"Dependent {} is read-only, implement Creator and/or Updater interfaces to modify it",
getClass().getSimpleName());
}
}