Skip to content

Consider a simpler Matcher interface #986

@metacosm

Description

@metacosm

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());
    }
  }

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions