diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..62e19f3b69 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,219 @@ +# AI Agents Guide for Java Operator SDK + +This document provides guidance for AI coding agents working with the Java Operator SDK codebase. + +## Project Overview + +Java Operator SDK is a production-ready framework for building Kubernetes Operators in Java. It provides: +- A controller runtime for reconciliation loops +- Support for dependent resources and workflows +- Testing utilities for operator development +- Integration with Fabric8 Kubernetes Client + +**Key Technologies:** +- Java 17 (compilation target), validated in CI against Java 17, 21, and 25 +- Maven for build management +- Fabric8 Kubernetes Client for K8s API access +- JUnit 6 (via `org.junit:junit-bom`) for testing +- GitHub Actions for CI/CD + +## Project Structure + +### Core Modules + +``` +java-operator-sdk/ +├── operator-framework-core/ # Core reconciliation engine and API +├── operator-framework/ # Main operator framework implementation +├── operator-framework-junit5/ # Testing utilities and extensions +├── operator-framework-bom/ # Bill of Materials for dependency management +├── micrometer-support/ # Metrics integration +├── open-telemetry-support/ # Distributed tracing support +├── caffeine-bounded-cache-support/ # Caching implementation +├── bootstrapper-maven-plugin/ # Maven plugin for bootstrapping +└── test-index-processor/ # Test utilities for annotation processing +``` + +### Key Packages + +- `io.javaoperatorsdk.operator.api.reconciler` - Core reconciler interfaces and annotations +- `io.javaoperatorsdk.operator.processing` - Event processing and workflow engine +- `io.javaoperatorsdk.operator.processing.dependent` - Dependent resource management +- `io.javaoperatorsdk.operator.api.config` - Configuration interfaces +- `io.javaoperatorsdk.operator.junit` - Testing support classes + +## Working Effectively + +### Build Commands + +```bash +# Full build with tests +./mvnw clean install + +# Build without tests +./mvnw clean install -DskipTests + +# Parallel build (uses 1 thread per CPU core) +./mvnw -T1C clean install + +# Check code formatting +./mvnw spotless:check + +# Apply code formatting +./mvnw spotless:apply + +# Check license headers +./mvnw -N license:check +``` + +### Test Execution + +```bash +# Run unit tests only +./mvnw test + +# Run integration tests +./mvnw verify -Pintegration-tests + +# Run specific test class +./mvnw test -Dtest=ClassName + +# Run specific test method +./mvnw test -Dtest=ClassName#methodName +``` + +## Code Conventions + +### Code Style + +- Formatting: The project uses Spotless with Google Java Format +- License Headers: All source files must have Apache 2.0 license headers +- Line Length: 100 characters maximum +- Indentation: 2 spaces (no tabs) +- Prefer `var` to avoid type declarations, except for very short type names like `int`, `long`, `String` etc. +- Always use proper imports for classes instead of fully qualified class references. Import classes at the top of the file and use simple class names throughout the code, only using fully qualified names when absolutely necessary to avoid naming collisions. +- Add unit and/or integration tests for new functionality whenever reasonably possible +- Avoid excessive logging, only add logs to critical parts. Avoid both logging errors and throwing exceptions at the same time. Throwing the error is enough it is logged already somewhere else. +- Do not add comments to the code, except in case of very long or complex logic. + +### Naming Conventions + +- **Reconcilers:** End with `Reconciler` (e.g., `MyResourceReconciler`) +- **Dependent Resources:** End with `DependentResource` (e.g., `ConfigMapDependentResource`) +- **Test Classes:** End with `Test` for unit tests, `IT` for integration tests, `E2E` for end-to-end testing. +- **Custom Resources:** Typically structured as `{Name}Spec`, `{Name}Status`, `{Name}` (the CR class) + +### API Design + +- Use builder patterns for complex configurations +- Prefer immutable objects where possible +- Use annotations for declarative configuration (`@ControllerConfiguration`, `@KubernetesDependent`, etc.) +- Follow fluent API design for DSLs + +## Testing Guidelines + +### Unit Tests + +- Use JUnit 6 +- Mock Kubernetes API interactions using Fabric8's mock server or Mockito; or service layer directly +- Test reconciliation logic in isolation +- Place in `src/test/java` + +### Integration Tests + +- Use `LocallyRunOperatorExtension` or `OperatorExtension` from `operator-framework-junit5` +- Test against real Kubernetes API (typically via test cluster like minikube or kind) +- Suffix with `IT` (e.g., `MyReconcilerIT`) +- Located in `src/test/java` + +### Test Resources + +- Kubernetes manifests in `src/test/resources` + +## Common Patterns + +### Reconciler Implementation + +Reconcilers implement the `Reconciler` interface: + +```java +@ControllerConfiguration +public class MyReconciler implements Reconciler { + + @Override + public UpdateControl reconcile( + MyCustomResource resource, Context context) { + // Reconciliation logic + return UpdateControl.noUpdate(); + } +} +``` + +### Dependent Resources + +Dependent resources use the `DependentResource` interface or extend base classes: + +```java +@KubernetesDependent +public class ConfigMapDependent extends CRUDKubernetesDependentResource { + + @Override + protected ConfigMap desired(Primary primary, Context context) { + // Return desired state + } +} +``` + +### Error Handling + +- Use `UpdateControl` with `rescheduleAfter()` for retriable errors +- Throw `RuntimeException` for non-retriable errors +- Update resource status to reflect error conditions +- Use structured logging (SLF4J) + +## Making Changes + +### Before Submitting a PR + +1. Run `./mvnw spotless:apply` to format code +2. Run `./mvnw clean install` to ensure all tests pass +3. Add tests for new functionality +4. Update documentation if adding/changing APIs +5. Follow existing code patterns and conventions + +### PR Guidelines + +- Keep changes focused and atomic +- We use conventional commits +- Reference issues in commit messages when applicable +- Ensure CI checks pass (format, license, tests) + +## Common Issues and Solutions + +### Build Issues + +- **Test failures:** Check if Kubernetes context is needed for ITs +- **Formatting failures:** Run `./mvnw spotless:apply` before committing + +### Test Issues + +- **Integration tests hanging:** Check for resource leaks or improper cleanup +- **Flaky tests:** Ensure proper synchronization using `StatusChecker` or similar patterns +- **Performance test variance:** Results depend on available CPU/memory + +## Resources + +- **Documentation:** https://javaoperatorsdk.io/ +- **GitHub:** https://github.com/operator-framework/java-operator-sdk +- **Slack:** [#java-operator-sdk](https://kubernetes.slack.com/archives/CAW0GV7A5) on Kubernetes Slack +- **Discord:** https://discord.gg/DacEhAy +- **Fabric8 Client:** https://github.com/fabric8io/kubernetes-client + + +## Additional Notes for AI Agents + +- The codebase makes extensive use of Java generics for type safety +- Context objects provide access to client, informers, and event sources +- The framework handles K8s API retries and conflict resolution automatically +- Prefer using `ResourceOperations` from context over direct client calls +- Status updates are handled separately from spec reconciliation diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000000..ac534a3109 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENT.md \ No newline at end of file diff --git a/README.md b/README.md index 5bb2758ae5..4836bd36e2 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,8 @@ Documentation can be found on the **[JOSDK WebSite](https://javaoperatorsdk.io/ Join us on [Discord](https://discord.gg/DacEhAy) or feel free to ask any question on [Kubernetes Slack Operator Channel](https://kubernetes.slack.com/archives/CAW0GV7A5) -**Meet us** every other Tuesday 15:00 CEST (from 29.10.2024) at our **community meeting** on [Zoom](https://zoom.us/j/8415370125) -(Password in the Discord channel, or just ask for it there!) +**Meet us** every other Tuesday 15:00 CEST +at our **community meeting**. [Download ics](https://raw.githubusercontent.com/operator-framework/java-operator-sdk/refs/heads/main/invite.ics) ## How to Contribute diff --git a/adr/001-Introducing-ADRs.md b/adr/001-Introducing-ADRs.md deleted file mode 100644 index 3a432e8094..0000000000 --- a/adr/001-Introducing-ADRs.md +++ /dev/null @@ -1,29 +0,0 @@ -# Using Architectural Decision Records - -In order to into to document and facilitate discussion over architecture and other design question of the project -we introduce usage of [ADR](https://adr.github.io/). - -In each ADR file, write these sections: - -# Title - -## Status - -What is the status, such as proposed, accepted, rejected, deprecated, superseded, etc.? - -## Context - -What is the issue that we're seeing that is motivating this decision or change? - -## Decision - -What is the change that we're proposing and/or doing? - -## Consequences - -What becomes easier or more difficult to do because of this change? - -## Notes - -Other notes optionally added to the ADR. -Soo other good materials for the ADRs: \ No newline at end of file diff --git a/adr/002-Custom-Resource-Deserialization-Problem.md b/adr/002-Custom-Resource-Deserialization-Problem.md deleted file mode 100644 index 0f648105fc..0000000000 --- a/adr/002-Custom-Resource-Deserialization-Problem.md +++ /dev/null @@ -1,44 +0,0 @@ -# Multi Version Custom Resources Deserialization Problem - -## Status - -accepted - -## Context - -In case there are multiple versions of a custom resource it can happen that a controller/informer tracking -such a resource might run into deserialization problem as shown -in [this integration test](https://github.com/operator-framework/java-operator-sdk/blob/07aab1a9914d865364d7236e496ef9ba5b50699e/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultiVersionCRDIT.java#L55-L55) -. -Such case is possible (as seen in the test) if there are no conversion hooks in place, so the two custom resources -which have different version are stored in the original form (not converted) and are not compatible. -In this case, if there is no further filtering (by labels) informer receives both, but naturally not able to deserialize -one of them. - -How should the framework or the underlying informer behave? - -Alternatives: - -1. The informer should skip the resource and should continue to process the resources with the correct version. -2. Informer stops and makes a notification callback. - -## Decision - -From the JOSDK perspective, it is fine if the informer stops, and the users decides if the whole operator should stop -(usually the preferred way). The reason, that this is an obvious issue on platform level (not on operator/controller -level). Thus, the controller should not receive such custom resources in the first place, so the problem should be -addressed at the platform level. Possibly introducing conversion hooks, or labeling for the target resource. - -## Consequences - -If an Informer stops on such deserialization error, even explicitly restarting it won't solve the problem, since -would fail again on the same error. - -## Notes - -- The informer implementation in fabric8 client changed in this regard, before it was not stopping on deserialization - error, but as described this change in behavior is completely acceptable. - -- the deserializer can be set to be more lenient by configuring the Serialization Unmatched Field Type module: - `Serialization.UNMATCHED_FIELD_TYPE_MODULE.setRestrictToTemplates(true);`. In general is not desired to - process custom resources that are not deserialized correctly. \ No newline at end of file diff --git a/bootstrapper-maven-plugin/pom.xml b/bootstrapper-maven-plugin/pom.xml index 6f7a7a2718..8626f78cb7 100644 --- a/bootstrapper-maven-plugin/pom.xml +++ b/bootstrapper-maven-plugin/pom.xml @@ -22,7 +22,7 @@ io.javaoperatorsdk java-operator-sdk - 5.2.2-SNAPSHOT + 5.2.3-SNAPSHOT bootstrapper @@ -33,7 +33,7 @@ 3.15.2 3.9.12 - 3.0.0 + 3.1.0 3.15.2 diff --git a/caffeine-bounded-cache-support/pom.xml b/caffeine-bounded-cache-support/pom.xml index c2a4fa5f84..151083a082 100644 --- a/caffeine-bounded-cache-support/pom.xml +++ b/caffeine-bounded-cache-support/pom.xml @@ -21,7 +21,7 @@ io.javaoperatorsdk java-operator-sdk - 5.2.2-SNAPSHOT + 5.2.3-SNAPSHOT caffeine-bounded-cache-support diff --git a/docs/content/en/docs/documentation/_index.md b/docs/content/en/docs/documentation/_index.md index 59373c6974..40522d9653 100644 --- a/docs/content/en/docs/documentation/_index.md +++ b/docs/content/en/docs/documentation/_index.md @@ -18,7 +18,7 @@ This section contains detailed documentation for all Java Operator SDK features ## Advanced Features - **[Eventing](eventing/)** - Understanding the event-driven model -- **[Accessing Resources in Caches](working-with-es-caches/) - How to access resources in caches +- **[Accessing Resources in Caches](working-with-es-caches/)** - How to access resources in caches - **[Observability](observability/)** - Monitoring and debugging your operators - **[Other Features](features/)** - Additional capabilities and integrations diff --git a/docs/content/en/docs/documentation/dependent-resource-and-workflows/dependent-resources.md b/docs/content/en/docs/documentation/dependent-resource-and-workflows/dependent-resources.md index 1981729dc0..f731f8939d 100644 --- a/docs/content/en/docs/documentation/dependent-resource-and-workflows/dependent-resources.md +++ b/docs/content/en/docs/documentation/dependent-resource-and-workflows/dependent-resources.md @@ -16,15 +16,26 @@ each secondary resource: ```mermaid flowchart TD - -compute[Compute desired secondary resource based on primary state] --> A -A{Secondary resource exists?} -A -- Yes --> match -A -- No --> Create --> Done - -match{Matches desired state?} -match -- Yes --> Done -match -- No --> Update --> Done + Start([Start Reconciliation]) --> Compute + Compute[Compute desired secondary
resource based on
primary state] --> Exists + Exists{Secondary
resource
exists?} + Exists -->|No| Create[Create Resource] + Exists -->|Yes| Match + Match{Matches
desired
state?} + Match -->|Yes| Done + Match -->|No| Update[Update Resource] + Create --> Done([Done]) + Update --> Done + + classDef startEnd fill:#e1f5e1,stroke:#4caf50,stroke-width:2px + classDef compute fill:#e3f2fd,stroke:#2196f3,stroke-width:2px + classDef decision fill:#fff3e0,stroke:#ff9800,stroke-width:2px + classDef action fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px + + class Start,Done startEnd + class Compute compute + class Exists,Match decision + class Create,Update action ``` While these steps are not difficult in and of themselves, there are some subtleties that can lead to diff --git a/invite.ics b/invite.ics new file mode 100644 index 0000000000..98e58f5806 --- /dev/null +++ b/invite.ics @@ -0,0 +1,48 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VTIMEZONE +TZID:America/New_York +X-LIC-LOCATION:America/New_York +BEGIN:DAYLIGHT +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +TZNAME:EDT +DTSTART:19700308T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +TZNAME:EST +DTSTART:19701101T020000 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTART;TZID=America/New_York:20260203T090000 +DTEND;TZID=America/New_York:20260203T100000 +RRULE:FREQ=WEEKLY;WKST=SU;INTERVAL=2;BYDAY=TU +DTSTAMP:20260205T122901Z +ORGANIZER:mailto:csviri@gmail.com +UID:0slja1g4au8gb1lfkfe0ladnll_R20241126T140000@google.com +X-MICROSOFT-CDO-OWNERAPPTID:1947686914 +CREATED:20231002T122647Z +DESCRIPTION:CNCF Operator SDK is inviting you to a scheduled community Zoom meeting. \n + Zoom: https://zoom.us/j/8415370125 \n + Password: 77777 +LAST-MODIFIED:20260205T122857Z +LOCATION: +SEQUENCE:5 +STATUS:CONFIRMED +SUMMARY:Java Operator SDK Community Meeting +TRANSP:OPAQUE +BEGIN:VALARM +ACTION:DISPLAY +DESCRIPTION:This is an event reminder +TRIGGER:-P0DT0H10M0S +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/micrometer-support/pom.xml b/micrometer-support/pom.xml index 1056cb7626..56b78197a5 100644 --- a/micrometer-support/pom.xml +++ b/micrometer-support/pom.xml @@ -21,7 +21,7 @@ io.javaoperatorsdk java-operator-sdk - 5.2.2-SNAPSHOT + 5.2.3-SNAPSHOT micrometer-support diff --git a/operator-framework-bom/pom.xml b/operator-framework-bom/pom.xml index 97ea2aca4c..8793a087fa 100644 --- a/operator-framework-bom/pom.xml +++ b/operator-framework-bom/pom.xml @@ -21,7 +21,7 @@ io.javaoperatorsdk operator-framework-bom - 5.2.2-SNAPSHOT + 5.2.3-SNAPSHOT pom Operator SDK - Bill of Materials Java SDK for implementing Kubernetes operators @@ -54,8 +54,8 @@ 3.2.8 3.4.0 3.12.0 - 3.1.0 - 0.9.0 + 3.2.1 + 0.10.0 diff --git a/operator-framework-core/pom.xml b/operator-framework-core/pom.xml index 6a02b69313..3b9bcbc471 100644 --- a/operator-framework-core/pom.xml +++ b/operator-framework-core/pom.xml @@ -21,7 +21,7 @@ io.javaoperatorsdk java-operator-sdk - 5.2.2-SNAPSHOT + 5.2.3-SNAPSHOT ../pom.xml @@ -137,7 +137,7 @@ org.codehaus.mojo templating-maven-plugin - 3.0.0 + 3.1.0 filtering-java-templates diff --git a/operator-framework-junit5/pom.xml b/operator-framework-junit5/pom.xml index ce7900e12b..62e351241a 100644 --- a/operator-framework-junit5/pom.xml +++ b/operator-framework-junit5/pom.xml @@ -21,7 +21,7 @@ io.javaoperatorsdk java-operator-sdk - 5.2.2-SNAPSHOT + 5.2.3-SNAPSHOT operator-framework-junit-5 diff --git a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java index 0a33293b53..5ea82026c3 100644 --- a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java +++ b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java @@ -67,6 +67,7 @@ public class LocallyRunOperatorExtension extends AbstractOperatorExtension { private final List portForwards; private final List localPortForwards; private final List> additionalCustomResourceDefinitions; + private final List additionalCustomResourceDefinitionInstances; private final Map registeredControllers; private final Map crdMappings; private final Consumer beforeStartHook; @@ -76,6 +77,7 @@ private LocallyRunOperatorExtension( List infrastructure, List portForwards, List> additionalCustomResourceDefinitions, + List additionalCustomResourceDefinitionInstances, Duration infrastructureTimeout, boolean preserveNamespaceOnError, boolean waitForNamespaceDeletion, @@ -101,6 +103,7 @@ private LocallyRunOperatorExtension( this.portForwards = portForwards; this.localPortForwards = new ArrayList<>(portForwards.size()); this.additionalCustomResourceDefinitions = additionalCustomResourceDefinitions; + this.additionalCustomResourceDefinitionInstances = additionalCustomResourceDefinitionInstances; this.beforeStartHook = beforeStartHook; configurationServiceOverrider = configurationServiceOverrider != null @@ -172,7 +175,7 @@ private static void applyCrd(String crdString, String path, KubernetesClient cli LOGGER.debug("Applying CRD: {}", crdString); final var crd = client.load(new ByteArrayInputStream(crdString.getBytes())); crd.serverSideApply(); - appliedCRDs.add(new AppliedCRD(crdString, path)); + appliedCRDs.add(new AppliedCRD.FileCRD(crdString, path)); Thread.sleep(CRD_READY_WAIT); // readiness is not applicable for CRD, just wait a little LOGGER.debug("Applied CRD with path: {}", path); } catch (InterruptedException ex) { @@ -195,6 +198,33 @@ public void applyCrd(Class crClass) { applyCrd(ReconcilerUtils.getResourceTypeName(crClass)); } + public void applyCrd(CustomResourceDefinition customResourceDefinition) { + try { + String resourceTypeName = customResourceDefinition.getMetadata().getName(); + final var pathAsString = crdMappings.get(resourceTypeName); + if (pathAsString != null) { + applyCrdFromMappings(pathAsString, resourceTypeName); + } else { + var resource = getKubernetesClient().resource(customResourceDefinition); + resource.serverSideApply(); + Thread.sleep(CRD_READY_WAIT); // readiness is not applicable for CRD, just wait a little + appliedCRDs.add(new AppliedCRD.InstanceCRD(customResourceDefinition)); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void applyCrdFromMappings(String pathAsString, String resourceTypeName) { + final var path = Path.of(pathAsString); + try { + applyCrd(Files.readString(path), pathAsString, getKubernetesClient()); + } catch (IOException e) { + throw new IllegalStateException("Cannot open CRD file at " + path.toAbsolutePath(), e); + } + crdMappings.remove(resourceTypeName); + } + /** * Applies the CRD associated with the specified resource type name, first checking if a CRD has * been manually specified using {@link Builder#withAdditionalCRD}, otherwise assuming that its @@ -209,13 +239,7 @@ public void applyCrd(String resourceTypeName) { // first attempt to use a manually defined CRD final var pathAsString = crdMappings.get(resourceTypeName); if (pathAsString != null) { - final var path = Path.of(pathAsString); - try { - applyCrd(Files.readString(path), pathAsString, getKubernetesClient()); - } catch (IOException e) { - throw new IllegalStateException("Cannot open CRD file at " + path.toAbsolutePath(), e); - } - crdMappings.remove(resourceTypeName); + applyCrdFromMappings(pathAsString, resourceTypeName); } else { // if no manually defined CRD matches the resource type, apply the generated one applyCrd(resourceTypeName, getKubernetesClient()); @@ -280,6 +304,7 @@ protected void before(ExtensionContext context) { } additionalCustomResourceDefinitions.forEach(this::applyCrd); + additionalCustomResourceDefinitionInstances.forEach(this::applyCrd); for (var ref : reconcilers) { final var config = operator.getConfigurationService().getConfigurationFor(ref.reconciler); final var oconfig = override(config); @@ -361,24 +386,60 @@ private void deleteCrd(AppliedCRD appliedCRD, KubernetesClient client) { LOGGER.debug("Skipping deleting CRD because of configuration: {}", appliedCRD); return; } - try { - LOGGER.debug("Deleting CRD: {}", appliedCRD.crdString); - final var crd = client.load(new ByteArrayInputStream(appliedCRD.crdString.getBytes())); - crd.withTimeoutInMillis(CRD_DELETE_TIMEOUT).delete(); - LOGGER.debug("Deleted CRD with path: {}", appliedCRD.path); - } catch (Exception ex) { - LOGGER.warn( - "Cannot delete CRD yaml: {}. You might need to delete it manually.", appliedCRD.path, ex); - } + appliedCRD.delete(client); } - private record AppliedCRD(String crdString, String path) {} + private sealed interface AppliedCRD permits AppliedCRD.FileCRD, AppliedCRD.InstanceCRD { + /** + * Delete this CRD from the cluster + * + * @param client client to use for deletion + */ + void delete(KubernetesClient client); + + record FileCRD(String crdString, String path) implements AppliedCRD { + + @Override + public void delete(KubernetesClient client) { + try { + LOGGER.debug("Deleting CRD: {}", crdString); + final var crd = client.load(new ByteArrayInputStream(crdString.getBytes())); + crd.withTimeoutInMillis(CRD_DELETE_TIMEOUT).delete(); + LOGGER.debug("Deleted CRD with path: {}", path); + } catch (Exception ex) { + LOGGER.warn( + "Cannot delete CRD yaml: {}. You might need to delete it manually.", path, ex); + } + } + } + + record InstanceCRD(CustomResourceDefinition customResourceDefinition) implements AppliedCRD { + + @Override + public void delete(KubernetesClient client) { + String type = customResourceDefinition.getMetadata().getName(); + try { + LOGGER.debug("Deleting CustomResourceDefinition instance CRD: {}", type); + final var crd = client.resource(customResourceDefinition); + crd.withTimeoutInMillis(CRD_DELETE_TIMEOUT).delete(); + LOGGER.debug("Deleted CustomResourceDefinition instance CRD: {}", type); + } catch (Exception ex) { + LOGGER.warn( + "Cannot delete CustomResourceDefinition instance CRD: {}. You might need to delete it" + + " manually.", + type, + ex); + } + } + } + } @SuppressWarnings("rawtypes") public static class Builder extends AbstractBuilder { private final List reconcilers; private final List portForwards; private final List> additionalCustomResourceDefinitions; + private final List additionalCustomResourceDefinitionInstances; private final List additionalCRDs = new ArrayList<>(); private Consumer beforeStartHook; private KubernetesClient kubernetesClient; @@ -389,6 +450,7 @@ protected Builder() { this.reconcilers = new ArrayList<>(); this.portForwards = new ArrayList<>(); this.additionalCustomResourceDefinitions = new ArrayList<>(); + this.additionalCustomResourceDefinitionInstances = new ArrayList<>(); } public Builder withReconciler( @@ -449,6 +511,11 @@ public Builder withAdditionalCustomResourceDefinition( return this; } + public Builder withAdditionalCustomResourceDefinition(CustomResourceDefinition definition) { + additionalCustomResourceDefinitionInstances.add(definition); + return this; + } + public Builder withAdditionalCRD(String... paths) { if (paths != null) { additionalCRDs.addAll(List.of(paths)); @@ -471,6 +538,7 @@ public LocallyRunOperatorExtension build() { infrastructure, portForwards, additionalCustomResourceDefinitions, + additionalCustomResourceDefinitionInstances, infrastructureTimeout, preserveNamespaceOnError, waitForNamespaceDeletion, diff --git a/operator-framework/pom.xml b/operator-framework/pom.xml index b8ff58f418..549d6b9e2e 100644 --- a/operator-framework/pom.xml +++ b/operator-framework/pom.xml @@ -21,7 +21,7 @@ io.javaoperatorsdk java-operator-sdk - 5.2.2-SNAPSHOT + 5.2.3-SNAPSHOT operator-framework diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/CRDMappingInTestExtensionIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/CRDMappingInTestExtensionIT.java index 366698fc66..72614e3fb2 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/CRDMappingInTestExtensionIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/CRDMappingInTestExtensionIT.java @@ -16,14 +16,21 @@ package io.javaoperatorsdk.operator; import java.time.Duration; +import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionBuilder; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionList; +import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaPropsBuilder; import io.fabric8.kubernetes.client.CustomResource; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientBuilder; +import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; +import io.fabric8.kubernetes.client.dsl.Resource; import io.fabric8.kubernetes.model.annotation.Group; import io.fabric8.kubernetes.model.annotation.Kind; import io.fabric8.kubernetes.model.annotation.Version; @@ -44,7 +51,8 @@ Demonstrates how to manually specify and apply Custom Resource Definitions (CRDs) in \ integration tests using the LocallyRunOperatorExtension. This test verifies that CRDs \ can be loaded from specified file paths and properly registered with the Kubernetes API \ - server during test execution. + server during test execution. It also verifies that CustomResourceDefinition instances + with no corresponding file can be applied. """) public class CRDMappingInTestExtensionIT { private final KubernetesClient client = new KubernetesClientBuilder().build(); @@ -54,34 +62,75 @@ public class CRDMappingInTestExtensionIT { LocallyRunOperatorExtension.builder() .withReconciler(new TestReconciler()) .withAdditionalCRD("src/test/resources/crd/test.crd", "src/test/crd/test.crd") + .withAdditionalCustomResourceDefinition(testCRD()) .build(); + public static CustomResourceDefinition testCRD() { + return new CustomResourceDefinitionBuilder() + .editOrNewSpec() + .withScope("Cluster") + .withGroup("operator.javaoperatorsdk.io") + .editOrNewNames() + .withPlural("tests") + .withSingular("test") + .withKind("Test") + .endNames() + .addNewVersion() + .withName("v1") + .withServed(true) + .withStorage(true) + .withNewSchema() + .withNewOpenAPIV3Schema() + .withType("object") + .withProperties(Map.of("bar", new JSONSchemaPropsBuilder().withType("string").build())) + .endOpenAPIV3Schema() + .endSchema() + .endVersion() + .and() + .editOrNewMetadata() + .withName("tests.operator.javaoperatorsdk.io") + .and() + .build(); + } + @Test void correctlyAppliesManuallySpecifiedCRD() { final var crdClient = client.apiextensions().v1().customResourceDefinitions(); + await() + .pollDelay(Duration.ofMillis(150)) + .untilAsserted(() -> assertCrdApplied(crdClient, "tests.crd.example", "foo")); await() .pollDelay(Duration.ofMillis(150)) .untilAsserted( - () -> { - final var actual = crdClient.withName("tests.crd.example").get(); - assertThat(actual).isNotNull(); - assertThat( - actual - .getSpec() - .getVersions() - .get(0) - .getSchema() - .getOpenAPIV3Schema() - .getProperties() - .containsKey("foo")) - .isTrue(); - }); + () -> assertCrdApplied(crdClient, "tests.operator.javaoperatorsdk.io", "bar")); await() .pollDelay(Duration.ofMillis(150)) .untilAsserted( () -> assertThat(crdClient.withName("externals.crd.example").get()).isNotNull()); } + private static void assertCrdApplied( + NonNamespaceOperation< + CustomResourceDefinition, + CustomResourceDefinitionList, + Resource> + crdClient, + String s, + String propertyName) { + final var actual = crdClient.withName(s).get(); + assertThat(actual).isNotNull(); + assertThat( + actual + .getSpec() + .getVersions() + .get(0) + .getSchema() + .getOpenAPIV3Schema() + .getProperties() + .containsKey(propertyName)) + .isTrue(); + } + @Group("crd.example") @Version("v1") @Kind("Test") diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/IntegrationTestConstants.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/IntegrationTestConstants.java index 5be26e1c93..96a8b9119a 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/IntegrationTestConstants.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/IntegrationTestConstants.java @@ -15,7 +15,11 @@ */ package io.javaoperatorsdk.operator; +import java.time.Duration; + public class IntegrationTestConstants { public static final int GARBAGE_COLLECTION_TIMEOUT_SECONDS = 60; + + public static final Duration GARBAGE_COLLECTION_TIMEOUT = Duration.ofSeconds(60); } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ConcurrencyIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ConcurrencyIT.java index 7a9282dcce..8522fb30e7 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ConcurrencyIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ConcurrencyIT.java @@ -25,7 +25,6 @@ import org.slf4j.LoggerFactory; import io.fabric8.kubernetes.api.model.ConfigMap; -import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.baseapi.simple.TestCustomResource; import io.javaoperatorsdk.operator.baseapi.simple.TestReconciler; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; @@ -34,15 +33,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; -@Sample( - tldr = "Concurrent Reconciliation of Multiple Resources", - description = - """ - Demonstrates the operator's ability to handle concurrent reconciliation of multiple \ - resources. The test creates, updates, and deletes many resources simultaneously to \ - verify proper handling of concurrent operations, ensuring thread safety and correct \ - resource state management under load. - """) class ConcurrencyIT { public static final int NUMBER_OF_RESOURCES_CREATED = 50; public static final int NUMBER_OF_RESOURCES_DELETED = 30; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/StopWithoutStartIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/StopWithoutStartIT.java index fdf5865d3e..086d5486ff 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/StopWithoutStartIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/StopWithoutStartIT.java @@ -19,22 +19,12 @@ import org.junit.jupiter.api.Timeout; import io.fabric8.kubernetes.api.model.ConfigMap; -import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.Operator; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; -@Sample( - tldr = "Stop Operator Without Starting", - description = - """ - Demonstrates that an operator can be stopped without being started. This is important \ - for cleanup scenarios where an operator is created and registered with reconcilers but \ - never started due to initialization failures or other conditions. The stop() method \ - properly cleans up thread pools even when the operator was never started. - """) class StopWithoutStartIT { @Test diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/expectation/onallevent/ExpectationIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/expectation/onallevent/ExpectationIT.java index fee3b3c583..49666f15f2 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/expectation/onallevent/ExpectationIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/expectation/onallevent/ExpectationIT.java @@ -24,6 +24,7 @@ import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; +import static io.javaoperatorsdk.operator.IntegrationTestConstants.GARBAGE_COLLECTION_TIMEOUT; import static io.javaoperatorsdk.operator.baseapi.expectation.onallevent.ExpectationReconciler.DEPLOYMENT_READY; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; @@ -52,6 +53,7 @@ void testExpectation() { extension.create(res); await() + .timeout(GARBAGE_COLLECTION_TIMEOUT) .untilAsserted( () -> { var actual = extension.get(ExpectationCustomResource.class, TEST_1); @@ -67,6 +69,7 @@ void expectationTimeouts() { extension.create(res); await() + .timeout(GARBAGE_COLLECTION_TIMEOUT) .untilAsserted( () -> { var actual = extension.get(ExpectationCustomResource.class, TEST_1); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/expectation/periodicclean/PeriodicCleanerExpectationIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/expectation/periodicclean/PeriodicCleanerExpectationIT.java index fc31b8e2ec..bc41c4d82f 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/expectation/periodicclean/PeriodicCleanerExpectationIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/expectation/periodicclean/PeriodicCleanerExpectationIT.java @@ -24,6 +24,7 @@ import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; +import static io.javaoperatorsdk.operator.IntegrationTestConstants.GARBAGE_COLLECTION_TIMEOUT; import static io.javaoperatorsdk.operator.baseapi.expectation.periodicclean.PeriodicCleanerExpectationReconciler.DEPLOYMENT_READY; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; @@ -57,6 +58,7 @@ void testPeriodicCleanerExpectationBasicFlow() { extension.create(res); await() + .timeout(GARBAGE_COLLECTION_TIMEOUT) .untilAsserted( () -> { var actual = extension.get(PeriodicCleanerExpectationCustomResource.class, TEST_1); @@ -80,6 +82,7 @@ void demonstratesNoTriggerReconcilerOnAllEventsNeededForCleanup() { var created = extension.create(res); await() + .timeout(GARBAGE_COLLECTION_TIMEOUT) .untilAsserted( () -> { assertThat(reconciler.getExpectationManager().getExpectation(created)).isPresent(); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/InformerErrorHandlerStartIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerstarterror/InformerErrorHandlerStartIT.java similarity index 97% rename from operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/InformerErrorHandlerStartIT.java rename to operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerstarterror/InformerErrorHandlerStartIT.java index b10591461c..3f062baf75 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/InformerErrorHandlerStartIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerstarterror/InformerErrorHandlerStartIT.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.javaoperatorsdk.operator.baseapi; +package io.javaoperatorsdk.operator.baseapi.informerstarterror; import java.time.Duration; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/LeaderElectionPermissionIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/leaderelectionpermission/LeaderElectionPermissionIT.java similarity index 98% rename from operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/LeaderElectionPermissionIT.java rename to operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/leaderelectionpermission/LeaderElectionPermissionIT.java index db99324ae2..0180e3b8b8 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/LeaderElectionPermissionIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/leaderelectionpermission/LeaderElectionPermissionIT.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.javaoperatorsdk.operator.baseapi; +package io.javaoperatorsdk.operator.baseapi.leaderelectionpermission; import org.junit.jupiter.api.Test; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/standalonedependent/StandaloneDependentResourceIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/standalonedependent/StandaloneDependentResourceIT.java index 3d9c15e807..a44b3e13f3 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/standalonedependent/StandaloneDependentResourceIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/standalonedependent/StandaloneDependentResourceIT.java @@ -93,7 +93,7 @@ void executeUpdateForTestingCacheUpdateForGetResource() { void awaitForDeploymentReadyReplicas(int expectedReplicaCount) { await() .pollInterval(Duration.ofMillis(300)) - .atMost(Duration.ofSeconds(50)) + .atMost(Duration.ofSeconds(180)) .until( () -> { var deployment = diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/complexdependent/ComplexWorkflowIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/complexdependent/ComplexWorkflowIT.java index 6357ec7888..5cc7814517 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/complexdependent/ComplexWorkflowIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/complexdependent/ComplexWorkflowIT.java @@ -55,7 +55,7 @@ void successfullyReconciles() { operator.create(testResource()); await() - .atMost(Duration.ofSeconds(90)) + .atMost(Duration.ofSeconds(120)) .untilAsserted( () -> { var res = operator.get(ComplexWorkflowCustomResource.class, TEST_RESOURCE_NAME); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowallfeature/WorkflowAllFeatureIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowallfeature/WorkflowAllFeatureIT.java index 41f68e42f7..4dc02c3fe9 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowallfeature/WorkflowAllFeatureIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowallfeature/WorkflowAllFeatureIT.java @@ -27,6 +27,7 @@ import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; +import static io.javaoperatorsdk.operator.IntegrationTestConstants.GARBAGE_COLLECTION_TIMEOUT; import static io.javaoperatorsdk.operator.workflow.workflowallfeature.ConfigMapDependentResource.READY_TO_DELETE_ANNOTATION; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; @@ -69,7 +70,7 @@ void configMapNotReconciledUntilDeploymentReady() { }); await() - .atMost(ONE_MINUTE) + .timeout(ONE_MINUTE) .untilAsserted( () -> { assertThat( @@ -107,6 +108,7 @@ void configMapNotReconciledIfReconcileConditionNotMet() { operator.replace(resource); await() + .timeout(GARBAGE_COLLECTION_TIMEOUT) .untilAsserted( () -> { assertThat(operator.get(ConfigMap.class, RESOURCE_NAME)).isNotNull(); @@ -141,7 +143,7 @@ void configMapNotDeletedUntilNotMarked() { markConfigMapForDelete(); await() - .atMost(ONE_MINUTE) + .timeout(GARBAGE_COLLECTION_TIMEOUT) .untilAsserted( () -> { assertThat(operator.get(ConfigMap.class, RESOURCE_NAME)).isNull(); diff --git a/operator-framework/src/test/resources/io/javaoperatorsdk/operator/baseapi/leader-elector-stop-noaccess-role-binding.yaml b/operator-framework/src/test/resources/io/javaoperatorsdk/operator/baseapi/leaderelectionpermission/leader-elector-stop-noaccess-role-binding.yaml similarity index 100% rename from operator-framework/src/test/resources/io/javaoperatorsdk/operator/baseapi/leader-elector-stop-noaccess-role-binding.yaml rename to operator-framework/src/test/resources/io/javaoperatorsdk/operator/baseapi/leaderelectionpermission/leader-elector-stop-noaccess-role-binding.yaml diff --git a/operator-framework/src/test/resources/io/javaoperatorsdk/operator/baseapi/leader-elector-stop-role-noaccess.yaml b/operator-framework/src/test/resources/io/javaoperatorsdk/operator/baseapi/leaderelectionpermission/leader-elector-stop-role-noaccess.yaml similarity index 100% rename from operator-framework/src/test/resources/io/javaoperatorsdk/operator/baseapi/leader-elector-stop-role-noaccess.yaml rename to operator-framework/src/test/resources/io/javaoperatorsdk/operator/baseapi/leaderelectionpermission/leader-elector-stop-role-noaccess.yaml diff --git a/pom.xml b/pom.xml index 990ce50386..fb19f9b97f 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ io.javaoperatorsdk java-operator-sdk - 5.2.2-SNAPSHOT + 5.2.3-SNAPSHOT pom Operator SDK for Java Java SDK for implementing Kubernetes operators @@ -71,26 +71,26 @@ https://sonarcloud.io jdk 6.0.2 - 7.4.0 + 7.5.2 2.0.17 2.25.3 5.21.0 3.20.0 0.23.0 1.13.0 - 3.27.6 + 3.27.7 4.3.0 2.7.3 - 1.16.1 + 1.16.3 3.2.3 0.9.14 2.21.0 4.16 2.11 - 3.14.1 + 3.15.0 3.5.4 - 0.9.0 + 0.10.0 3.12.0 3.4.0 3.4.0 @@ -102,7 +102,7 @@ 3.1.4 9.0.2 3.5.1 - 3.1.0 + 3.2.1 diff --git a/sample-operators/controller-namespace-deletion/pom.xml b/sample-operators/controller-namespace-deletion/pom.xml index 564bc778fe..67b04eb31a 100644 --- a/sample-operators/controller-namespace-deletion/pom.xml +++ b/sample-operators/controller-namespace-deletion/pom.xml @@ -22,7 +22,7 @@ io.javaoperatorsdk sample-operators - 5.2.2-SNAPSHOT + 5.2.3-SNAPSHOT sample-controller-namespace-deletion diff --git a/sample-operators/leader-election/pom.xml b/sample-operators/leader-election/pom.xml index 2364408c59..1feb5ffb2e 100644 --- a/sample-operators/leader-election/pom.xml +++ b/sample-operators/leader-election/pom.xml @@ -22,7 +22,7 @@ io.javaoperatorsdk sample-operators - 5.2.2-SNAPSHOT + 5.2.3-SNAPSHOT sample-leader-election diff --git a/sample-operators/mysql-schema/pom.xml b/sample-operators/mysql-schema/pom.xml index f0909d6179..77b105e0c6 100644 --- a/sample-operators/mysql-schema/pom.xml +++ b/sample-operators/mysql-schema/pom.xml @@ -22,7 +22,7 @@ io.javaoperatorsdk sample-operators - 5.2.2-SNAPSHOT + 5.2.3-SNAPSHOT sample-mysql-schema-operator diff --git a/sample-operators/pom.xml b/sample-operators/pom.xml index 05e04cda31..9387ffaa9a 100644 --- a/sample-operators/pom.xml +++ b/sample-operators/pom.xml @@ -22,7 +22,7 @@ io.javaoperatorsdk java-operator-sdk - 5.2.2-SNAPSHOT + 5.2.3-SNAPSHOT sample-operators diff --git a/sample-operators/tomcat-operator/pom.xml b/sample-operators/tomcat-operator/pom.xml index a8a2fdc3d5..1d58e67575 100644 --- a/sample-operators/tomcat-operator/pom.xml +++ b/sample-operators/tomcat-operator/pom.xml @@ -22,7 +22,7 @@ io.javaoperatorsdk sample-operators - 5.2.2-SNAPSHOT + 5.2.3-SNAPSHOT sample-tomcat-operator diff --git a/sample-operators/webpage/pom.xml b/sample-operators/webpage/pom.xml index 8916f79a03..f6fa568dd1 100644 --- a/sample-operators/webpage/pom.xml +++ b/sample-operators/webpage/pom.xml @@ -22,7 +22,7 @@ io.javaoperatorsdk sample-operators - 5.2.2-SNAPSHOT + 5.2.3-SNAPSHOT sample-webpage-operator diff --git a/test-index-processor/pom.xml b/test-index-processor/pom.xml index ee8558b419..e9ac365ad9 100644 --- a/test-index-processor/pom.xml +++ b/test-index-processor/pom.xml @@ -22,7 +22,7 @@ io.javaoperatorsdk java-operator-sdk - 5.2.2-SNAPSHOT + 5.2.3-SNAPSHOT test-index-processor