Skip to content

[Question] desired method of CRUDKubernetesDependentResource called twice when dependent resource exists #2895

@michaelkoepf

Description

@michaelkoepf

Hi,

I have an operator that uses a standalone dependent resource. The dependent resource is of type CRUDKubernetesDependentResource<ConfigMap, T> and only overrides method protected ConfigMap desired(T primary, Context<T> context). When the dependent resource does not exist yet, desired is called once. However, when the dependent resource exists, desired is called twice in a row. Why is that? According to the flowchart in the docs, I would have expected only one call.

Minimal reproducible example based on the WebPage Example Operator

Full source code can be found here: https://github.com/michaelkoepf/dependent-resource-test. Most important classes can be found below.

@ControllerConfiguration
public class WebPageStandaloneDependentsReconciler implements Reconciler<WebPage> {

  private static final Logger LOG = LoggerFactory.getLogger(WebPageStandaloneDependentsReconciler.class);

  private final Workflow<WebPage> workflow;

  public WebPageStandaloneDependentsReconciler() {
    workflow = createDependentResourcesAndWorkflow();
  }

  @Override
  public List<EventSource<?, WebPage>> prepareEventSources(EventSourceContext<WebPage> context) {
    return EventSourceUtils.eventSourcesFromWorkflow(context, workflow);
  }

  @Override
  public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> context)
      throws Exception {
    LOG.info("Starting reconciliation at {}", System.currentTimeMillis());
    workflow.reconcile(webPage, context);

    return UpdateControl.noUpdate();
  }

  @SuppressWarnings({"unchecked", "rawtypes"})
  private Workflow<WebPage> createDependentResourcesAndWorkflow() {
    // create the dependent resources
    var configMapDR = new ConfigMapDependentResource();

    // configure them with our label selector
    Arrays.asList(configMapDR)
        .forEach(
            dr ->
                dr.configureWith(
                    new KubernetesDependentResourceConfigBuilder()
                        .withKubernetesDependentInformerConfig(
                            InformerConfiguration.builder(dr.resourceType())
                                .withLabelSelector("managed=true")
                                .build())
                        .build()));

    return new WorkflowBuilder<WebPage>()
        .addDependentResource(configMapDR)
        .build();
  }
}
public class ConfigMapDependentResource
    extends CRUDKubernetesDependentResource<ConfigMap, WebPage> {

  private static final Logger LOG = LoggerFactory.getLogger(ConfigMapDependentResource.class);

  @Override
  protected ConfigMap desired(WebPage webPage, Context<WebPage> context) {
    LOG.info("Evaluating desired state at {}", System.currentTimeMillis());

    Map<String, String> data = new HashMap<>();
    data.put("index.html", webPage.getSpec().getHtml());
    Map<String, String> labels = new HashMap<>();
    labels.put("managed", "true");
    return new ConfigMapBuilder()
        .withMetadata(
            new ObjectMetaBuilder()
                .withName("webpage-configmap")
                .withNamespace(webPage.getMetadata().getNamespace())
                .withLabels(labels)
                .build())
        .withData(data)
        .build();
  }
}

Example

  1. Trigger reconciliation by kubectl apply -f k8s/webpage-v1.yaml. Dependent resource does not exist yet. Log:
2025-08-11 13:32:02,237 INFO  [com.exa.WebPageStandaloneDependentsReconciler] (ReconcilerExecutor-webpagestandalonedependentsreconciler-185) Starting reconciliation at 1754911922237
2025-08-11 13:32:02,240 INFO  [com.exa.ConfigMapDependentResource] (pool-16-thread-1) Evaluating desired state at 1754911922240
  1. Dependent resource now exists. Trigger reconciliation again by kubectl apply -f k8s/webpage-v2.yaml. Log:
2025-08-11 13:32:04,816 INFO  [com.exa.WebPageStandaloneDependentsReconciler] (ReconcilerExecutor-webpagestandalonedependentsreconciler-188) Starting reconciliation at 1754911924816
2025-08-11 13:32:04,817 INFO  [com.exa.ConfigMapDependentResource] (pool-16-thread-2) Evaluating desired state at 1754911924817
2025-08-11 13:32:04,818 INFO  [com.exa.ConfigMapDependentResource] (pool-16-thread-2) Evaluating desired state at 1754911924818
  1. Trigger reconciliation again by kubectl apply -f k8s/webpage-v3.yaml. Log:
2025-08-11 13:32:06,888 INFO  [com.exa.WebPageStandaloneDependentsReconciler] (ReconcilerExecutor-webpagestandalonedependentsreconciler-191) Starting reconciliation at 1754911926888
2025-08-11 13:32:06,889 INFO  [com.exa.ConfigMapDependentResource] (pool-16-thread-3) Evaluating desired state at 1754911926889
2025-08-11 13:32:06,889 INFO  [com.exa.ConfigMapDependentResource] (pool-16-thread-3) Evaluating desired state at 1754911926889

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions