diff --git a/.github/workflows/hugo.yaml b/.github/workflows/hugo.yaml index 2c0a63d50d..50a2d83182 100644 --- a/.github/workflows/hugo.yaml +++ b/.github/workflows/hugo.yaml @@ -45,6 +45,16 @@ jobs: with: submodules: recursive fetch-depth: 0 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: maven + - name: Build test-index-processor and generate test documentation + run: | + ./mvnw clean install -DskipTests -pl test-index-processor + ./mvnw process-test-classes -DskipTests -pl operator-framework - name: Setup Pages id: pages uses: actions/configure-pages@v5 diff --git a/operator-framework/pom.xml b/operator-framework/pom.xml index 1cf2a0aa9e..6dc9549be0 100644 --- a/operator-framework/pom.xml +++ b/operator-framework/pom.xml @@ -84,6 +84,13 @@ kube-api-test-client-inject test + + io.javaoperatorsdk + test-index-processor + ${project.version} + test + true + @@ -106,6 +113,23 @@ + + + default-testCompile + + testCompile + + test-compile + + + + io.javaoperatorsdk + test-index-processor + ${project.version} + + + + @@ -138,6 +162,50 @@ org.apache.maven.plugins maven-surefire-plugin + + org.apache.maven.plugins + maven-resources-plugin + + + copy-samples-to-docs + + copy-resources + + process-test-classes + + ${project.basedir}/../docs/content/en/docs/testindex + + + ${project.build.directory}/generated-test-sources/test-annotations + + samples.md + + false + + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + 3.1.0 + + + rename-samples-to-index + + run + + process-test-classes + + + + + + + + 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 80fb09095d..e1139695b0 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 @@ -10,6 +10,7 @@ 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; @@ -18,6 +19,13 @@ 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/builtinresourcecleaner/BuiltInResourceCleanerIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/builtinresourcecleaner/BuiltInResourceCleanerIT.java index 9d914d080c..6e3a7a2721 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/builtinresourcecleaner/BuiltInResourceCleanerIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/builtinresourcecleaner/BuiltInResourceCleanerIT.java @@ -8,6 +8,7 @@ import org.slf4j.LoggerFactory; import io.fabric8.kubernetes.api.model.Service; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.ReconcilerUtils; import io.javaoperatorsdk.operator.dependent.standalonedependent.StandaloneDependentResourceIT; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; @@ -15,6 +16,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Cleanup handler for built-in Kubernetes resources", + description = + "Demonstrates how to implement cleanup handlers (finalizers) for built-in Kubernetes" + + " resources like Service and Pod. These resources don't use generation the same way" + + " as custom resources, so this sample shows the proper approach to handle their" + + " lifecycle and cleanup logic.") class BuiltInResourceCleanerIT { private static final Logger log = LoggerFactory.getLogger(BuiltInResourceCleanerIT.class); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/changenamespace/ChangeNamespaceIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/changenamespace/ChangeNamespaceIT.java index 67f65c64ca..0e4948dd9f 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/changenamespace/ChangeNamespaceIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/changenamespace/ChangeNamespaceIT.java @@ -14,6 +14,7 @@ import io.fabric8.kubernetes.api.model.NamespaceBuilder; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.client.KubernetesClient; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.RegisteredController; import io.javaoperatorsdk.operator.api.reconciler.Constants; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; @@ -21,6 +22,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Dynamically Changing Watched Namespaces", + description = + "Demonstrates how to dynamically change the set of namespaces that an operator watches at" + + " runtime. This feature allows operators to add or remove namespaces from their watch" + + " list, including switching between specific namespaces and watching all namespaces." + + " The test verifies that resources in newly added namespaces are reconciled and" + + " resources in removed namespaces are no longer watched.") class ChangeNamespaceIT { public static final String TEST_RESOURCE_NAME_1 = "test1"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cleanerforreconciler/CleanerForReconcilerIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cleanerforreconciler/CleanerForReconcilerIT.java index 6ccb34f0e3..b3e753443a 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cleanerforreconciler/CleanerForReconcilerIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/cleanerforreconciler/CleanerForReconcilerIT.java @@ -4,11 +4,21 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Implementing Cleanup Logic with Cleaner Interface", + description = + "Demonstrates how to implement cleanup logic for custom resources using the Cleaner" + + " interface. When a reconciler implements Cleaner, the framework automatically adds a" + + " finalizer to resources and calls the cleanup method when the resource is deleted." + + " This pattern is useful for cleaning up external resources or performing custom" + + " deletion logic. The test verifies finalizer handling, cleanup execution, and the" + + " ability to reschedule cleanup operations.") class CleanerForReconcilerIT { public static final String TEST_RESOURCE_NAME = "cleaner-for-reconciler-test1"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/clusterscopedresource/ClusterScopedResourceIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/clusterscopedresource/ClusterScopedResourceIT.java index 4c92fbada7..27de87c62d 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/clusterscopedresource/ClusterScopedResourceIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/clusterscopedresource/ClusterScopedResourceIT.java @@ -7,12 +7,20 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static io.javaoperatorsdk.operator.IntegrationTestConstants.GARBAGE_COLLECTION_TIMEOUT_SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Cluster-scoped resource reconciliation", + description = + "Demonstrates how to reconcile cluster-scoped custom resources (non-namespaced). This" + + " test shows CRUD operations on cluster-scoped resources and verifies that" + + " dependent resources are created, updated, and properly cleaned up when the" + + " primary resource is deleted.") class ClusterScopedResourceIT { public static final String TEST_NAME = "test1"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/createupdateeventfilter/CreateUpdateInformerEventSourceEventFilterIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/createupdateeventfilter/CreateUpdateInformerEventSourceEventFilterIT.java index 2d9a7db573..261f6681c2 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/createupdateeventfilter/CreateUpdateInformerEventSourceEventFilterIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/createupdateeventfilter/CreateUpdateInformerEventSourceEventFilterIT.java @@ -7,12 +7,20 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static io.javaoperatorsdk.operator.baseapi.createupdateeventfilter.CreateUpdateEventFilterTestReconciler.CONFIG_MAP_TEST_DATA_KEY; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Event filtering for create and update operations", + description = + "Shows how to configure event filters on informer event sources to control which create and" + + " update events trigger reconciliation. This is useful for preventing unnecessary" + + " reconciliation loops when dependent resources are modified by the controller" + + " itself.") class CreateUpdateInformerEventSourceEventFilterIT { @RegisterExtension diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/deployment/KubernetesResourceStatusUpdateIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/deployment/KubernetesResourceStatusUpdateIT.java index 5fe9bfa939..7f3b5019a6 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/deployment/KubernetesResourceStatusUpdateIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/deployment/KubernetesResourceStatusUpdateIT.java @@ -17,12 +17,21 @@ import io.fabric8.kubernetes.api.model.PodTemplateSpec; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.apps.DeploymentSpec; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static io.javaoperatorsdk.operator.baseapi.deployment.DeploymentReconciler.STATUS_MESSAGE; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Reconciling Non-Custom Kubernetes Resources with Status Updates", + description = + "Demonstrates how to reconcile standard Kubernetes resources (like Deployments) instead of" + + " custom resources, and how to update their status subresource. This pattern is" + + " useful when building operators that manage native Kubernetes resources rather than" + + " custom resource definitions. The test verifies that the operator can watch," + + " reconcile, and update the status of a Deployment resource.") class KubernetesResourceStatusUpdateIT { @RegisterExtension diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/errorstatushandler/ErrorStatusHandlerIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/errorstatushandler/ErrorStatusHandlerIT.java index b888143d2d..a78b00eb8c 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/errorstatushandler/ErrorStatusHandlerIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/errorstatushandler/ErrorStatusHandlerIT.java @@ -6,12 +6,20 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import io.javaoperatorsdk.operator.processing.retry.GenericRetry; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Error Status Handler for Failed Reconciliations", + description = + "Demonstrates how to implement error status handlers that update resource status when" + + " reconciliations fail. The test verifies that error messages are properly recorded" + + " in the resource status after each failed retry attempt. This provides visibility" + + " into reconciliation failures and helps with debugging operator issues.") class ErrorStatusHandlerIT { public static final int MAX_RETRY_ATTEMPTS = 3; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/event/EventSourceIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/event/EventSourceIT.java index eaa881709b..a6b64bb512 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/event/EventSourceIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/event/EventSourceIT.java @@ -6,12 +6,20 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import io.javaoperatorsdk.operator.support.TestUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Custom Event Source for Periodic Reconciliation", + description = + "Demonstrates how to implement custom event sources that trigger reconciliation on a" + + " periodic basis. The test verifies that reconciliations are triggered at regular" + + " intervals by a timer-based event source. This enables operators to perform periodic" + + " checks or updates independent of resource changes.") class EventSourceIT { @RegisterExtension diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/filter/FilterIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/filter/FilterIT.java index 1e51ad5ab0..0cfd447620 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/filter/FilterIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/filter/FilterIT.java @@ -6,12 +6,20 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static io.javaoperatorsdk.operator.baseapi.filter.FilterTestReconciler.CONFIG_MAP_FILTER_VALUE; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Filtering Events for Primary and Secondary Resources", + description = + "Demonstrates how to implement event filters for both primary custom resources and" + + " secondary dependent resources. The test verifies that resource updates matching" + + " specific filter criteria are ignored and don't trigger reconciliation. This helps" + + " reduce unnecessary reconciliation executions and improve operator efficiency.") class FilterIT { public static final String RESOURCE_NAME = "test1"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/generickubernetesresourcehandling/GenericKubernetesResourceHandlingIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/generickubernetesresourcehandling/GenericKubernetesResourceHandlingIT.java index 3b2fccfe59..a6b2687c58 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/generickubernetesresourcehandling/GenericKubernetesResourceHandlingIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/generickubernetesresourcehandling/GenericKubernetesResourceHandlingIT.java @@ -3,10 +3,19 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.dependent.generickubernetesresource.GenericKubernetesDependentSpec; import io.javaoperatorsdk.operator.dependent.generickubernetesresource.GenericKubernetesDependentTestBase; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; +@Sample( + tldr = "Working with GenericKubernetesResource for Dynamic Resource Types", + description = + "Demonstrates how to use GenericKubernetesResource to work with Kubernetes resources" + + " dynamically without requiring compile-time type definitions. This approach is" + + " useful when building operators that need to manage arbitrary Kubernetes resources" + + " or when the resource types are not known at compile time. The test shows how to" + + " handle generic resources as dependent resources in a reconciler.") public class GenericKubernetesResourceHandlingIT extends GenericKubernetesDependentTestBase { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/gracefulstop/GracefulStopIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/gracefulstop/GracefulStopIT.java index 4f499b256b..620dc31a6c 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/gracefulstop/GracefulStopIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/gracefulstop/GracefulStopIT.java @@ -6,12 +6,20 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static io.javaoperatorsdk.operator.baseapi.gracefulstop.GracefulStopTestReconciler.RECONCILER_SLEEP; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Graceful Operator Shutdown with Reconciliation Timeout", + description = + "Demonstrates how to configure graceful shutdown behavior with reconciliation termination" + + " timeouts. The test verifies that in-progress reconciliations are allowed to" + + " complete when the operator stops. This ensures clean shutdown without interrupting" + + " ongoing reconciliation work.") public class GracefulStopIT { public static final String TEST_1 = "test1"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informereventsource/InformerEventSourceIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informereventsource/InformerEventSourceIT.java index c1a857c22c..cf29212504 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informereventsource/InformerEventSourceIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informereventsource/InformerEventSourceIT.java @@ -8,6 +8,7 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static io.javaoperatorsdk.operator.baseapi.informereventsource.InformerEventSourceTestCustomReconciler.MISSING_CONFIG_MAP; @@ -17,6 +18,13 @@ import static org.assertj.core.api.Assertions.fail; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Using Informer Event Source to Watch Secondary Resources", + description = + "Demonstrates how to use InformerEventSource to watch changes in secondary resources" + + " (ConfigMaps) and trigger reconciliation when those resources are created, updated," + + " or deleted. The test verifies that the reconciler responds to ConfigMap changes and" + + " updates the primary resource status accordingly.") class InformerEventSourceIT { public static final String RESOURCE_NAME = "informertestcr"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterIT.java index 3cd5ddccbc..4cf676acd9 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster/InformerRemoteClusterIT.java @@ -10,6 +10,7 @@ import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.client.KubernetesClient; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import io.javaoperatorsdk.operator.processing.event.source.informer.Mappers; @@ -17,6 +18,12 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Watching resources in a remote Kubernetes cluster", + description = + "Demonstrates how to configure an informer event source to watch resources in a different" + + " Kubernetes cluster from where the operator is running. This enables multi-cluster" + + " scenarios where an operator in one cluster manages resources in another cluster.") @EnableKubeAPIServer class InformerRemoteClusterIT { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/labelselector/LabelSelectorIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/labelselector/LabelSelectorIT.java index b073bee248..75fe13d292 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/labelselector/LabelSelectorIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/labelselector/LabelSelectorIT.java @@ -8,6 +8,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static io.javaoperatorsdk.operator.baseapi.labelselector.LabelSelectorTestReconciler.LABEL_KEY; @@ -15,6 +16,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Label Selector for Custom Resource Filtering", + description = + "Demonstrates how to configure label selectors to filter which custom resources an operator" + + " watches. The test verifies that only resources with matching labels trigger" + + " reconciliation. This allows operators to selectively manage a subset of custom" + + " resources based on their labels.") class LabelSelectorIT { @RegisterExtension diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/leaderelectionchangenamespace/LeaderElectionChangeNamespaceIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/leaderelectionchangenamespace/LeaderElectionChangeNamespaceIT.java index f6194439e2..eea3b105c7 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/leaderelectionchangenamespace/LeaderElectionChangeNamespaceIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/leaderelectionchangenamespace/LeaderElectionChangeNamespaceIT.java @@ -14,12 +14,20 @@ import io.fabric8.kubernetes.api.model.coordination.v1.LeaseSpecBuilder; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.api.config.LeaderElectionConfiguration; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Leader election with namespace change handling", + description = + "Tests that when an operator is not elected as leader, changing the watched namespaces does" + + " not start processing. This ensures that only the leader operator actively" + + " reconciles resources, preventing conflicts in multi-instance deployments with" + + " leader election.") public class LeaderElectionChangeNamespaceIT { public static final String LEASE_NAME = "nschangelease"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/manualobservedgeneration/ManualObservedGenerationIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/manualobservedgeneration/ManualObservedGenerationIT.java index f90fda5c5e..5a0a5e9277 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/manualobservedgeneration/ManualObservedGenerationIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/manualobservedgeneration/ManualObservedGenerationIT.java @@ -4,11 +4,18 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Manually managing observedGeneration in status", + description = + "Shows how to manually track and update the observedGeneration field in status to indicate" + + " which generation of the resource spec has been successfully processed. This is" + + " useful for providing clear feedback to users about reconciliation progress.") public class ManualObservedGenerationIT { public static final String RESOURCE_NAME = "test1"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/maxinterval/MaxIntervalIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/maxinterval/MaxIntervalIT.java index af7cdbe8ec..87ca4e8f39 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/maxinterval/MaxIntervalIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/maxinterval/MaxIntervalIT.java @@ -6,11 +6,19 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Maximum Reconciliation Interval Configuration", + description = + "Demonstrates how to configure a maximum interval for periodic reconciliation triggers. The" + + " test verifies that reconciliation is automatically triggered at the configured" + + " interval even when there are no resource changes, enabling periodic validation and" + + " drift detection.") class MaxIntervalIT { @RegisterExtension diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/multiplereconcilersametype/MultipleReconcilerSameTypeIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/multiplereconcilersametype/MultipleReconcilerSameTypeIT.java index 81cfa7fd07..4fa5f59d6d 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/multiplereconcilersametype/MultipleReconcilerSameTypeIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/multiplereconcilersametype/MultipleReconcilerSameTypeIT.java @@ -4,11 +4,19 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Multiple reconcilers for the same resource type", + description = + "Demonstrates how to register multiple reconcilers for the same custom resource type, with" + + " each reconciler handling different resources based on label selectors or other" + + " criteria. This enables different processing logic for different subsets of the same" + + " resource type.") public class MultipleReconcilerSameTypeIT { public static final String TEST_RESOURCE_1 = "test1"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/multiplesecondaryeventsource/MultipleSecondaryEventSourceIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/multiplesecondaryeventsource/MultipleSecondaryEventSourceIT.java index 18a937040f..eac4a1d4f9 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/multiplesecondaryeventsource/MultipleSecondaryEventSourceIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/multiplesecondaryeventsource/MultipleSecondaryEventSourceIT.java @@ -7,10 +7,18 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Managing Multiple Secondary Event Sources", + description = + "Demonstrates how to configure and use multiple secondary event sources for a single" + + " reconciler. The test verifies that the reconciler is triggered by changes to" + + " different secondary resources and handles events from multiple sources correctly," + + " including periodic event sources.") class MultipleSecondaryEventSourceIT { public static final String TEST_RESOURCE_NAME = "testresource"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/multiversioncrd/MultiVersionCRDIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/multiversioncrd/MultiVersionCRDIT.java index 9e0c6eb1fb..a5cc049065 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/multiversioncrd/MultiVersionCRDIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/multiversioncrd/MultiVersionCRDIT.java @@ -13,6 +13,7 @@ import io.fabric8.kubernetes.client.WatcherException; import io.fabric8.kubernetes.client.informers.SharedIndexInformer; import io.fabric8.kubernetes.client.utils.Serialization; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.api.config.InformerStoppedHandler; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; @@ -21,6 +22,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Handling Multiple CRD Versions", + description = + "Demonstrates how to work with Custom Resource Definitions that have multiple API versions." + + " The test shows how to configure multiple reconcilers for different versions of the" + + " same CRD, handle version-specific schemas, and deal with incompatible version" + + " conversions. It also demonstrates error handling through InformerStoppedHandler" + + " when deserialization fails due to schema incompatibilities between versions.") class MultiVersionCRDIT { private static final Logger log = LoggerFactory.getLogger(MultiVersionCRDIT.class); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/nextreconciliationimminent/NextReconciliationImminentIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/nextreconciliationimminent/NextReconciliationImminentIT.java index 03bd6b0a7a..db2fd5ab56 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/nextreconciliationimminent/NextReconciliationImminentIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/nextreconciliationimminent/NextReconciliationImminentIT.java @@ -8,11 +8,18 @@ import org.slf4j.LoggerFactory; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Skipping status updates when next reconciliation is imminent", + description = + "Shows how to use the nextReconciliationImminent flag to skip status updates when another" + + " reconciliation event is already pending. This optimization prevents unnecessary" + + " status patch operations when rapid consecutive reconciliations occur.") public class NextReconciliationImminentIT { private static final Logger log = LoggerFactory.getLogger(NextReconciliationImminentIT.class); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/patchresourceandstatusnossa/PatchResourceAndStatusNoSSAIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/patchresourceandstatusnossa/PatchResourceAndStatusNoSSAIT.java index a835dd2de6..f4f3e0bf3c 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/patchresourceandstatusnossa/PatchResourceAndStatusNoSSAIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/patchresourceandstatusnossa/PatchResourceAndStatusNoSSAIT.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import io.javaoperatorsdk.operator.support.TestUtils; @@ -15,6 +16,12 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Patching resource and status without Server-Side Apply", + description = + "Demonstrates how to patch both the primary resource metadata/spec and status subresource" + + " using traditional JSON merge patch instead of Server-Side Apply. This shows the" + + " legacy approach for updating resources when SSA is disabled.") class PatchResourceAndStatusNoSSAIT { @RegisterExtension LocallyRunOperatorExtension extension = diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/patchresourcewithssa/PatchResourceAndStatusWithSSAIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/patchresourcewithssa/PatchResourceAndStatusWithSSAIT.java index f395706092..b4f86bf701 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/patchresourcewithssa/PatchResourceAndStatusWithSSAIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/patchresourcewithssa/PatchResourceAndStatusWithSSAIT.java @@ -1,7 +1,15 @@ package io.javaoperatorsdk.operator.baseapi.patchresourcewithssa; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +@Sample( + tldr = "Patching resource and status with Server-Side Apply", + description = + "Demonstrates how to use Server-Side Apply (SSA) to patch both the primary resource and its" + + " status subresource. SSA provides better conflict resolution and field management" + + " tracking compared to traditional merge patches, making it the recommended approach" + + " for resource updates.") public class PatchResourceAndStatusWithSSAIT extends PatchWithSSAITBase { @Override diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/patchresourcewithssa/PatchResourceWithSSAIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/patchresourcewithssa/PatchResourceWithSSAIT.java index d512665d06..1f7ebe00c6 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/patchresourcewithssa/PatchResourceWithSSAIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/patchresourcewithssa/PatchResourceWithSSAIT.java @@ -1,7 +1,15 @@ package io.javaoperatorsdk.operator.baseapi.patchresourcewithssa; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +@Sample( + tldr = "Patching Resources with Server-Side Apply (SSA)", + description = + "Demonstrates how to use Server-Side Apply (SSA) for patching primary resources in" + + " Kubernetes. The test verifies that the reconciler can patch resources using SSA," + + " which provides better conflict resolution and field management compared to" + + " traditional update approaches, including proper handling of managed fields.") public class PatchResourceWithSSAIT extends PatchWithSSAITBase { @Override diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/perresourceeventsource/PerResourcePollingEventSourceIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/perresourceeventsource/PerResourcePollingEventSourceIT.java index 20f89e0d39..5dad4d6d64 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/perresourceeventsource/PerResourcePollingEventSourceIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/perresourceeventsource/PerResourcePollingEventSourceIT.java @@ -4,11 +4,18 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Per-resource polling event source implementation", + description = + "Shows how to implement a per-resource polling event source where each primary resource has" + + " its own polling schedule to fetch external state. This is useful for integrating" + + " with external systems that don't support event-driven notifications.") class PerResourcePollingEventSourceIT { public static final String NAME_1 = "name1"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primaryindexer/PrimaryIndexerIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primaryindexer/PrimaryIndexerIT.java index 9063ef0fcb..d07e3daeff 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primaryindexer/PrimaryIndexerIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primaryindexer/PrimaryIndexerIT.java @@ -7,12 +7,22 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static io.javaoperatorsdk.operator.baseapi.primaryindexer.AbstractPrimaryIndexerTestReconciler.CONFIG_MAP_NAME; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Using Primary Indexer for Secondary Resource Mapping", + description = + "Demonstrates how to use primary indexers to efficiently map secondary resources back to" + + " their primary resources. When a secondary resource (like a ConfigMap) changes, the" + + " primary indexer allows the framework to determine which primary resources should be" + + " reconciled. This pattern enables efficient one-to-many and many-to-many" + + " relationships between primary and secondary resources without polling or full" + + " scans.") public class PrimaryIndexerIT { public static final String RESOURCE_NAME1 = "test1"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/PrimaryToSecondaryIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/PrimaryToSecondaryIT.java index d82c24a55f..344df7d66d 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/PrimaryToSecondaryIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/PrimaryToSecondaryIT.java @@ -6,11 +6,19 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Primary to Secondary Resource Mapping", + description = + "Demonstrates many-to-one mapping between primary and secondary resources where multiple" + + " primary resources can reference the same secondary resource. The test verifies that" + + " changes in the secondary resource trigger reconciliation of all related primary" + + " resources, enabling shared resource patterns.") class PrimaryToSecondaryIT { public static final String CLUSTER_NAME = "cluster1"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ratelimit/RateLimitIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ratelimit/RateLimitIT.java index 8b04fd0095..3f50fd334d 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ratelimit/RateLimitIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/ratelimit/RateLimitIT.java @@ -9,12 +9,20 @@ import org.slf4j.LoggerFactory; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static io.javaoperatorsdk.operator.baseapi.ratelimit.RateLimitReconciler.REFRESH_PERIOD; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Rate Limiting Reconciliation Executions", + description = + "Demonstrates how to implement rate limiting to control how frequently reconciliations" + + " execute. The test shows that multiple rapid resource updates are batched and" + + " executed at a controlled rate. This prevents overwhelming the system when resources" + + " change frequently.") class RateLimitIT { private static final Logger log = LoggerFactory.getLogger(RateLimitIT.class); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/retry/RetryIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/retry/RetryIT.java index 410d34a390..d746747186 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/retry/RetryIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/retry/RetryIT.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import io.javaoperatorsdk.operator.processing.retry.GenericRetry; import io.javaoperatorsdk.operator.support.TestUtils; @@ -13,6 +14,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Automatic Retry for Failed Reconciliations", + description = + "Demonstrates how to configure automatic retry logic for reconciliations that fail" + + " temporarily. The test shows that failed executions are automatically retried with" + + " configurable intervals and max attempts. After a specified number of retries, the" + + " reconciliation succeeds and updates the resource status accordingly.") class RetryIT { public static final int RETRY_INTERVAL = 150; public static final int MAX_RETRY_ATTEMPTS = 5; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/retry/RetryMaxAttemptIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/retry/RetryMaxAttemptIT.java index f57a70201f..2835ab0353 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/retry/RetryMaxAttemptIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/retry/RetryMaxAttemptIT.java @@ -3,12 +3,20 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import io.javaoperatorsdk.operator.processing.retry.GenericRetry; import static io.javaoperatorsdk.operator.baseapi.retry.RetryIT.createTestCustomResource; import static org.assertj.core.api.Assertions.assertThat; +@Sample( + tldr = "Maximum Retry Attempts Configuration", + description = + "Demonstrates how to configure a maximum number of retry attempts for failed" + + " reconciliations. The test verifies that the operator stops retrying after reaching" + + " the configured maximum attempts. This prevents infinite retry loops when" + + " reconciliations consistently fail.") class RetryMaxAttemptIT { public static final int MAX_RETRY_ATTEMPTS = 3; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/simple/ReconcilerExecutorIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/simple/ReconcilerExecutorIT.java index cbd8de4459..5f916f3b9d 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/simple/ReconcilerExecutorIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/simple/ReconcilerExecutorIT.java @@ -7,12 +7,20 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.fabric8.kubernetes.api.model.ConfigMap; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import io.javaoperatorsdk.operator.support.TestUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Basic reconciler execution", + description = + "Demonstrates the basic reconciler execution flow including resource creation, status" + + " updates, and cleanup. This test verifies that a reconciler can create dependent" + + " resources (ConfigMap), update status, and properly handle cleanup when resources" + + " are deleted.") class ReconcilerExecutorIT { @RegisterExtension diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/StatusPatchCacheIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/StatusPatchCacheIT.java index 9d0b923056..b3ac2ba753 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/StatusPatchCacheIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuscache/StatusPatchCacheIT.java @@ -6,11 +6,19 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Status patch caching for consistency", + description = + "Demonstrates how the framework caches status patches to ensure consistency when status is" + + " updated frequently. The cache guarantees that status values are monotonically" + + " increasing and always reflect the most recent state, even with rapid successive" + + " updates.") public class StatusPatchCacheIT { public static final String TEST_1 = "test1"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statusupdatelocking/StatusUpdateLockingIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statusupdatelocking/StatusUpdateLockingIT.java index ad51d82059..56e2b9aa26 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statusupdatelocking/StatusUpdateLockingIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statusupdatelocking/StatusUpdateLockingIT.java @@ -7,12 +7,20 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static io.javaoperatorsdk.operator.baseapi.statusupdatelocking.StatusUpdateLockingReconciler.WAIT_TIME; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Status Update Locking and Concurrency Control", + description = + "Demonstrates how the framework handles concurrent status updates and ensures no optimistic" + + " locking conflicts occur when updating status subresources. The test verifies that" + + " status updates can proceed independently of spec updates without causing version" + + " conflicts or requiring retries.") class StatusUpdateLockingIT { public static final String TEST_RESOURCE_NAME = "test"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/subresource/SubResourceUpdateIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/subresource/SubResourceUpdateIT.java index 7544e3b791..84293520ba 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/subresource/SubResourceUpdateIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/subresource/SubResourceUpdateIT.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import io.javaoperatorsdk.operator.support.TestUtils; @@ -14,6 +15,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Status Subresource Updates", + description = + "Demonstrates how to properly update the status subresource of custom resources. The test" + + " verifies that status updates are handled correctly without triggering unnecessary" + + " reconciliations, and that concurrent spec and status updates are managed properly" + + " with optimistic locking and retry mechanisms.") class SubResourceUpdateIT { public static final int WAIT_AFTER_EXECUTION = 500; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/bulkdependent/managed/ManagedBulkDependentIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/bulkdependent/managed/ManagedBulkDependentIT.java index 61b7f62a24..b9630182eb 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/bulkdependent/managed/ManagedBulkDependentIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/bulkdependent/managed/ManagedBulkDependentIT.java @@ -2,9 +2,18 @@ import org.junit.jupiter.api.extension.RegisterExtension; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.dependent.bulkdependent.BulkDependentTestBase; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; +@Sample( + tldr = "Bulk Dependent Resources with Managed Workflow", + description = + "Demonstrates how to manage bulk dependent resources using the managed workflow approach." + + " This test extends the base bulk dependent test to show how multiple instances of" + + " the same type of dependent resource can be created and managed together. The" + + " managed workflow handles the orchestration of creating, updating, and deleting" + + " multiple dependent resources based on the primary resource specification.") public class ManagedBulkDependentIT extends BulkDependentTestBase { @RegisterExtension diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/bulkdependent/readonly/ReadOnlyBulkDependentIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/bulkdependent/readonly/ReadOnlyBulkDependentIT.java index 422360b7b2..b6742f32c4 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/bulkdependent/readonly/ReadOnlyBulkDependentIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/bulkdependent/readonly/ReadOnlyBulkDependentIT.java @@ -7,6 +7,7 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.dependent.bulkdependent.BulkDependentTestCustomResource; import io.javaoperatorsdk.operator.dependent.bulkdependent.BulkDependentTestSpec; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; @@ -14,6 +15,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Read-Only Bulk Dependent Resources", + description = + "Demonstrates how to use read-only bulk dependent resources to observe and react to" + + " multiple existing resources without managing them. This test shows how an operator" + + " can monitor a collection of resources created externally and update the custom" + + " resource status based on their state, without creating or modifying them.") public class ReadOnlyBulkDependentIT { public static final int EXPECTED_NUMBER_OF_RESOURCES = 2; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/bulkdependent/standalone/StandaloneBulkDependentIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/bulkdependent/standalone/StandaloneBulkDependentIT.java index a74102db0d..a5bc6abbed 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/bulkdependent/standalone/StandaloneBulkDependentIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/bulkdependent/standalone/StandaloneBulkDependentIT.java @@ -2,9 +2,17 @@ import org.junit.jupiter.api.extension.RegisterExtension; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.dependent.bulkdependent.BulkDependentTestBase; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; +@Sample( + tldr = "Standalone Bulk Dependent Resources", + description = + "Demonstrates how to use standalone bulk dependent resources to manage multiple" + + " resources of the same type efficiently. This test shows how bulk operations can be" + + " performed on a collection of resources without individual reconciliation cycles," + + " improving performance when managing many similar resources.") class StandaloneBulkDependentIT extends BulkDependentTestBase { @RegisterExtension diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/cleanermanageddependent/CleanerForManagedDependentResourcesOnlyIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/cleanermanageddependent/CleanerForManagedDependentResourcesOnlyIT.java index b0b716e8e2..f6d99df44f 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/cleanermanageddependent/CleanerForManagedDependentResourcesOnlyIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/cleanermanageddependent/CleanerForManagedDependentResourcesOnlyIT.java @@ -4,11 +4,19 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Cleanup handlers for managed dependent resources", + description = + "Shows how to implement cleanup logic for managed dependent resources using the Cleaner" + + " interface. The framework automatically adds finalizers and invokes the cleanup" + + " method when the primary resource is deleted, ensuring proper cleanup of dependent" + + " resources.") class CleanerForManagedDependentResourcesOnlyIT { public static final String TEST_RESOURCE_NAME = "cleaner-for-reconciler-test1"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/createonlyifnotexistsdependentwithssa/CreateOnlyIfNotExistingDependentWithSSAIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/createonlyifnotexistsdependentwithssa/CreateOnlyIfNotExistingDependentWithSSAIT.java index 3c41bae977..276cdb2214 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/createonlyifnotexistsdependentwithssa/CreateOnlyIfNotExistingDependentWithSSAIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/createonlyifnotexistsdependentwithssa/CreateOnlyIfNotExistingDependentWithSSAIT.java @@ -10,11 +10,19 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ConfigMapBuilder; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Create-Only Dependent Resources with Server-Side Apply", + description = + "Demonstrates how to configure a dependent resource that is only created if it doesn't" + + " exist, using Server-Side Apply (SSA). This test shows that when a resource already" + + " exists, the dependent resource implementation will not modify it, preserving any" + + " external changes.") class CreateOnlyIfNotExistingDependentWithSSAIT { public static final String TEST_RESOURCE_NAME = "test1"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentdifferentnamespace/DependentDifferentNamespaceIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentdifferentnamespace/DependentDifferentNamespaceIT.java index c02bac5a5d..dbffc4cdf6 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentdifferentnamespace/DependentDifferentNamespaceIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentdifferentnamespace/DependentDifferentNamespaceIT.java @@ -5,12 +5,21 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static io.javaoperatorsdk.operator.dependent.dependentdifferentnamespace.ConfigMapDependentResource.KEY; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Dependent Resources in Different Namespaces", + description = + "Demonstrates how to manage dependent resources in a namespace different from the primary" + + " resource. This test shows how to configure dependent resources to be created in a" + + " specific namespace rather than inheriting the namespace from the primary resource." + + " The test verifies full CRUD operations for a ConfigMap that lives in a different" + + " namespace than the custom resource that manages it.") class DependentDifferentNamespaceIT { public static final String TEST_1 = "different-ns-test1"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentfilter/DependentFilterIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentfilter/DependentFilterIT.java index bc7b578d7f..4ea2a82ce4 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentfilter/DependentFilterIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentfilter/DependentFilterIT.java @@ -8,6 +8,7 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static io.javaoperatorsdk.operator.dependent.dependentfilter.DependentFilterTestReconciler.CM_VALUE_KEY; @@ -15,6 +16,15 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Filtering Reconciliation Triggers from Dependent Resources", + description = + "Demonstrates how to filter events from dependent resources to prevent unnecessary" + + " reconciliation triggers. This test shows how to configure filters on dependent" + + " resources so that only specific changes trigger a reconciliation of the primary" + + " resource. The test verifies that updates to filtered fields in the dependent" + + " resource do not cause the reconciler to execute, improving efficiency and avoiding" + + " reconciliation loops.") class DependentFilterIT { public static final String RESOURCE_NAME = "test1"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentoperationeventfiltering/DependentOperationEventFilterIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentoperationeventfiltering/DependentOperationEventFilterIT.java index e9ddc6cd6e..efc5fc467b 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentoperationeventfiltering/DependentOperationEventFilterIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentoperationeventfiltering/DependentOperationEventFilterIT.java @@ -7,11 +7,19 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Event filtering for dependent resource operations", + description = + "Demonstrates how to configure event filters on dependent resources to prevent" + + " reconciliation loops. When a dependent resource is created or updated by the" + + " controller, the filter prevents those events from triggering unnecessary" + + " reconciliations.") class DependentOperationEventFilterIT { public static final String TEST = "test"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentssa/DependentSSAMatchingIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentssa/DependentSSAMatchingIT.java index 8ab686b1b8..0c44348006 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentssa/DependentSSAMatchingIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/dependentssa/DependentSSAMatchingIT.java @@ -11,11 +11,21 @@ import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.client.dsl.base.PatchContext; import io.fabric8.kubernetes.client.dsl.base.PatchType; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Server-Side Apply (SSA) with Dependent Resources", + description = + "Demonstrates how to use Server-Side Apply (SSA) with dependent resources and field manager" + + " matching. This test shows how SSA allows multiple controllers to manage different" + + " fields of the same resource without conflicts. The test verifies that changes made" + + " by different field managers are properly isolated, and that the operator only" + + " updates its own fields when changes occur, preserving fields managed by other" + + " controllers.") public class DependentSSAMatchingIT { public static final String TEST_RESOURCE_NAME = "test1"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/externalstate/ExternalStateIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/externalstate/ExternalStateIT.java index bae36431b5..985bafd541 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/externalstate/ExternalStateIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/externalstate/ExternalStateIT.java @@ -5,6 +5,7 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import io.javaoperatorsdk.operator.support.ExternalIDGenServiceMock; @@ -12,6 +13,15 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Managing External Resources with Persistent State", + description = + "Demonstrates how to manage external resources (outside of Kubernetes) while maintaining" + + " their state in Kubernetes resources. This test shows a pattern for reconciling" + + " external systems by storing external resource identifiers in a ConfigMap. The test" + + " verifies that external resources can be created, updated, and deleted in" + + " coordination with Kubernetes resources, with the ConfigMap serving as a state store" + + " for external resource IDs.") class ExternalStateIT { private static final String TEST_RESOURCE_NAME = "test1"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/generickubernetesresource/generickubernetesdependentresourcemanaged/GenericKubernetesDependentManagedIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/generickubernetesresource/generickubernetesdependentresourcemanaged/GenericKubernetesDependentManagedIT.java index 93fc34fbf0..7395dacbb6 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/generickubernetesresource/generickubernetesdependentresourcemanaged/GenericKubernetesDependentManagedIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/generickubernetesresource/generickubernetesdependentresourcemanaged/GenericKubernetesDependentManagedIT.java @@ -3,10 +3,18 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.dependent.generickubernetesresource.GenericKubernetesDependentSpec; import io.javaoperatorsdk.operator.dependent.generickubernetesresource.GenericKubernetesDependentTestBase; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; +@Sample( + tldr = "Generic Kubernetes Dependent Resource (Managed)", + description = + "Demonstrates how to use GenericKubernetesResource as a managed dependent resource. This" + + " test shows how to work with generic Kubernetes resources that don't have a specific" + + " Java model class, allowing the operator to manage any Kubernetes resource type" + + " dynamically.") public class GenericKubernetesDependentManagedIT extends GenericKubernetesDependentTestBase { diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/kubernetesdependentgarbagecollection/KubernetesDependentGarbageCollectionIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/kubernetesdependentgarbagecollection/KubernetesDependentGarbageCollectionIT.java index 8e08c4e739..941c43d067 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/kubernetesdependentgarbagecollection/KubernetesDependentGarbageCollectionIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/kubernetesdependentgarbagecollection/KubernetesDependentGarbageCollectionIT.java @@ -7,12 +7,22 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.IntegrationTestConstants; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Kubernetes Native Garbage Collection for Dependent Resources", + description = + "Demonstrates how to leverage Kubernetes native garbage collection for dependent resources" + + " using owner references. This test shows how dependent resources are automatically" + + " cleaned up by Kubernetes when the owner resource is deleted, and how to" + + " conditionally create or delete dependent resources based on the primary resource" + + " state. Owner references ensure that dependent resources don't outlive their" + + " owners.") class KubernetesDependentGarbageCollectionIT { public static final String TEST_RESOURCE_NAME = "test1"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipledependentresource/MultipleDependentResourceIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipledependentresource/MultipleDependentResourceIT.java index a7dfefdaa8..6552543f94 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipledependentresource/MultipleDependentResourceIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipledependentresource/MultipleDependentResourceIT.java @@ -7,6 +7,7 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static io.javaoperatorsdk.operator.dependent.multipledependentresource.MultipleDependentResourceConfigMap.DATA_KEY; @@ -16,6 +17,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Managing Multiple Dependent Resources", + description = + "Demonstrates how to manage multiple dependent resources from a single reconciler. This" + + " test shows how a single custom resource can create, update, and delete multiple" + + " ConfigMaps (or other Kubernetes resources) as dependents. The test verifies that" + + " all dependent resources are created together, updated together when the primary" + + " resource changes, and properly cleaned up when the primary resource is deleted.") public class MultipleDependentResourceIT { public static final String CHANGED_VALUE = "changed value"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multiplemanageddependentsametype/MultipleManagedDependentSameTypeIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multiplemanageddependentsametype/MultipleManagedDependentSameTypeIT.java index a835b5a255..76d6a03f44 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multiplemanageddependentsametype/MultipleManagedDependentSameTypeIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multiplemanageddependentsametype/MultipleManagedDependentSameTypeIT.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static io.javaoperatorsdk.operator.IntegrationTestConstants.GARBAGE_COLLECTION_TIMEOUT_SECONDS; @@ -14,6 +15,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Managing Multiple Dependent Resources of the Same Type", + description = + "Demonstrates how to manage multiple dependent resources of the same type from a single" + + " reconciler. This test shows how multiple ConfigMaps with the same type can be" + + " created, updated, and deleted as dependent resources of a custom resource," + + " verifying proper CRUD operations and garbage collection.") class MultipleManagedDependentSameTypeIT { public static final String TEST_RESOURCE_NAME = "test1"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/primarytosecondaydependent/PrimaryToSecondaryDependentIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/primarytosecondaydependent/PrimaryToSecondaryDependentIT.java index 11a080a8bf..06155d761c 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/primarytosecondaydependent/PrimaryToSecondaryDependentIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/primarytosecondaydependent/PrimaryToSecondaryDependentIT.java @@ -9,6 +9,7 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.Secret; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static io.javaoperatorsdk.operator.dependent.primarytosecondaydependent.ConfigMapDependent.TEST_CONFIG_MAP_NAME; @@ -17,6 +18,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Primary to Secondary Dependent Resource", + description = + "Demonstrates how to configure dependencies between dependent resources where one" + + " dependent resource (secondary) depends on another dependent resource (primary)." + + " This test shows how a Secret's creation can be conditioned on the state of a" + + " ConfigMap, illustrating the use of reconcile preconditions and dependent resource" + + " chaining.") class PrimaryToSecondaryDependentIT { public static final String TEST_CR_NAME = "test1"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/restart/OperatorRestartIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/restart/OperatorRestartIT.java index b8adb562dd..426c6d7c72 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/restart/OperatorRestartIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/restart/OperatorRestartIT.java @@ -3,12 +3,19 @@ import org.junit.jupiter.api.*; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.Operator; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Operator restart and state recovery", + description = + "Tests that an operator can be stopped and restarted while maintaining correct behavior." + + " After restart, the operator should resume processing existing resources without" + + " losing track of their state, demonstrating proper state recovery and persistence.") class OperatorRestartIT { private static final Operator operator = new Operator(o -> o.withCloseClientOnStop(false)); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/servicestrictmatcher/ServiceStrictMatcherIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/servicestrictmatcher/ServiceStrictMatcherIT.java index 72edd4ae40..cb0345fc70 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/servicestrictmatcher/ServiceStrictMatcherIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/servicestrictmatcher/ServiceStrictMatcherIT.java @@ -6,11 +6,18 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Strict matching for Service resources", + description = + "Shows how to use a strict matcher for Service dependent resources that correctly handles" + + " Service-specific fields. This prevents unnecessary updates when Kubernetes adds" + + " default values or modifies certain fields, avoiding reconciliation loops.") public class ServiceStrictMatcherIT { @RegisterExtension diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/specialresourcesdependent/SpecialResourcesDependentIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/specialresourcesdependent/SpecialResourcesDependentIT.java index 3d62512bd7..cc57e92036 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/specialresourcesdependent/SpecialResourcesDependentIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/specialresourcesdependent/SpecialResourcesDependentIT.java @@ -5,6 +5,7 @@ import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.ServiceAccount; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static io.javaoperatorsdk.operator.dependent.specialresourcesdependent.SpecialResourceSpec.CHANGED_VALUE; @@ -16,6 +17,13 @@ * Test for resources that are somehow special, currently mostly to cover the approach to handle * resources without spec. Not all the resources added here. */ +@Sample( + tldr = "Handling special Kubernetes resources without spec", + description = + "Demonstrates how to handle special built-in Kubernetes resources like ServiceAccount that" + + " don't have a spec field. These resources require different handling approaches" + + " since their configuration is stored directly in the resource body rather than in a" + + " spec section.") public class SpecialResourcesDependentIT { public static final String RESOURCE_NAME = "test1"; 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 91fbd64a19..796d6ce7e5 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 @@ -9,6 +9,7 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.api.config.*; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; @@ -16,6 +17,15 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Standalone Dependent Resources", + description = + "Demonstrates how to use standalone dependent resources that are managed independently" + + " without explicit workflow configuration. This test shows how dependent resources" + + " can be created and managed programmatically, with the dependent resource handling" + + " CRUD operations on a Kubernetes Deployment. The test verifies both creation and" + + " update scenarios, including cache updates when the dependent resource state" + + " changes.") public class StandaloneDependentResourceIT { public static final String DEPENDENT_TEST_NAME = "dependent-test1"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerIT.java index f54708a9ea..5110b68c1c 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/statefulsetdesiredsanitizer/StatefulSetDesiredSanitizerIT.java @@ -7,11 +7,18 @@ import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.apps.StatefulSet; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Sanitizing StatefulSet desired state for SSA", + description = + "Shows how to properly sanitize StatefulSet resources before using Server-Side Apply." + + " StatefulSets have immutable fields and server-managed fields that need to be" + + " removed from the desired state to prevent conflicts and unnecessary updates.") public class StatefulSetDesiredSanitizerIT { public static final String TEST_1 = "test1"; 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 4af8467c5c..330705611b 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 @@ -9,6 +9,7 @@ import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.apps.StatefulSet; import io.fabric8.kubernetes.client.readiness.Readiness; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import io.javaoperatorsdk.operator.workflow.complexdependent.dependent.FirstService; import io.javaoperatorsdk.operator.workflow.complexdependent.dependent.FirstStatefulSet; @@ -18,6 +19,12 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Complex Workflow with Multiple Dependents", + description = + "Demonstrates a complex workflow with multiple dependent resources (StatefulSets and" + + " Services) that have dependencies on each other. This test shows how to orchestrate" + + " the reconciliation of interconnected dependent resources in a specific order.") class ComplexWorkflowIT { public static final String TEST_RESOURCE_NAME = "test1"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/manageddependentdeletecondition/ManagedDependentDeleteConditionIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/manageddependentdeletecondition/ManagedDependentDeleteConditionIT.java index 482d2e5091..3e9c1c7b0e 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/manageddependentdeletecondition/ManagedDependentDeleteConditionIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/manageddependentdeletecondition/ManagedDependentDeleteConditionIT.java @@ -9,11 +9,19 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.Secret; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Managed Dependent Delete Condition", + description = + "Demonstrates how to use delete conditions to control when dependent resources can be" + + " deleted. This test shows how the primary resource deletion can be blocked until" + + " dependent resources are properly cleaned up, ensuring graceful shutdown and" + + " preventing orphaned resources.") public class ManagedDependentDeleteConditionIT { public static final String RESOURCE_NAME = "test1"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/multipledependentwithactivation/MultipleDependentWithActivationIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/multipledependentwithactivation/MultipleDependentWithActivationIT.java index 2360660adc..07b3aafdd7 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/multipledependentwithactivation/MultipleDependentWithActivationIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/multipledependentwithactivation/MultipleDependentWithActivationIT.java @@ -6,11 +6,19 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.Secret; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Multiple Dependents with Activation Conditions", + description = + "Demonstrates how to use activation conditions with multiple dependent resources. This test" + + " shows how different dependent resources can be dynamically enabled or disabled" + + " based on runtime conditions, allowing flexible workflow behavior that adapts to" + + " changing requirements.") public class MultipleDependentWithActivationIT { public static final String INITIAL_VALUE = "initial_value"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/orderedmanageddependent/OrderedManagedDependentIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/orderedmanageddependent/OrderedManagedDependentIT.java index ff25911a8c..a89dce4ab2 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/orderedmanageddependent/OrderedManagedDependentIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/orderedmanageddependent/OrderedManagedDependentIT.java @@ -6,11 +6,18 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Ordered Managed Dependent Resources", + description = + "Demonstrates how to control the order of reconciliation for managed dependent resources." + + " This test verifies that dependent resources are reconciled in a specific sequence," + + " ensuring proper orchestration when dependencies have ordering requirements.") class OrderedManagedDependentIT { @RegisterExtension diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowactivationcleanup/WorkflowActivationCleanupIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowactivationcleanup/WorkflowActivationCleanupIT.java index 80bbf22d40..3bd0da8bc1 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowactivationcleanup/WorkflowActivationCleanupIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowactivationcleanup/WorkflowActivationCleanupIT.java @@ -8,12 +8,19 @@ import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientBuilder; import io.fabric8.kubernetes.client.utils.KubernetesResourceUtil; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.Operator; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Workflow Activation Cleanup", + description = + "Demonstrates how workflow cleanup is handled when activation conditions are involved. This" + + " test verifies that resources are properly cleaned up on operator startup even when" + + " marked for deletion, ensuring no orphaned resources remain after restarts.") public class WorkflowActivationCleanupIT { private final KubernetesClient client = new KubernetesClientBuilder().build(); diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowactivationcondition/WorkflowActivationConditionIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowactivationcondition/WorkflowActivationConditionIT.java index a5b5b23fe3..c38e37cf84 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowactivationcondition/WorkflowActivationConditionIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowactivationcondition/WorkflowActivationConditionIT.java @@ -5,12 +5,20 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static io.javaoperatorsdk.operator.workflow.workflowactivationcondition.ConfigMapDependentResource.DATA_KEY; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Workflow Activation Condition", + description = + "Demonstrates how to use activation conditions to conditionally enable or disable parts of" + + " a workflow. This test shows how the workflow can adapt to different environments" + + " (e.g., vanilla Kubernetes vs. OpenShift) by activating only the relevant dependent" + + " resources based on runtime conditions.") public class WorkflowActivationConditionIT { public static final String TEST_RESOURCE_NAME = "test1"; 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 93551dcf43..1567ef13af 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 @@ -9,12 +9,20 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; 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; +@Sample( + tldr = "Comprehensive workflow with reconcile and delete conditions", + description = + "Demonstrates a complete workflow implementation including reconcile conditions, delete" + + " conditions, and ready conditions. Shows how to control when dependent resources are" + + " created or deleted based on conditions, and how to coordinate dependencies that" + + " must wait for others to be ready.") public class WorkflowAllFeatureIT { public static final String RESOURCE_NAME = "test"; diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowexplicitinvocation/WorkflowExplicitInvocationIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowexplicitinvocation/WorkflowExplicitInvocationIT.java index eaa799300f..801d92bacf 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowexplicitinvocation/WorkflowExplicitInvocationIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowexplicitinvocation/WorkflowExplicitInvocationIT.java @@ -7,11 +7,19 @@ import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.annotation.Sample; import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +@Sample( + tldr = "Workflow Explicit Invocation", + description = + "Demonstrates how to explicitly control when a workflow is invoked rather than having it" + + " run automatically on every reconciliation. This test shows how to programmatically" + + " trigger workflow execution and how cleanup is still performed even with explicit" + + " invocation.") public class WorkflowExplicitInvocationIT { public static final String RESOURCE_NAME = "test1"; diff --git a/pom.xml b/pom.xml index 9931eefb7f..c2e6ae8443 100644 --- a/pom.xml +++ b/pom.xml @@ -36,6 +36,7 @@ sample-operators caffeine-bounded-cache-support bootstrapper-maven-plugin + test-index-processor diff --git a/test-index-processor/pom.xml b/test-index-processor/pom.xml new file mode 100644 index 0000000000..dc3e84ab3e --- /dev/null +++ b/test-index-processor/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + + io.javaoperatorsdk + java-operator-sdk + 5.1.6-SNAPSHOT + + + test-index-processor + Test Index Annotation Processor + Annotation processor for generating integration test index documentation + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -proc:none + + + + + diff --git a/test-index-processor/src/main/java/io/javaoperatorsdk/annotation/Sample.java b/test-index-processor/src/main/java/io/javaoperatorsdk/annotation/Sample.java new file mode 100644 index 0000000000..3bfb1a4e79 --- /dev/null +++ b/test-index-processor/src/main/java/io/javaoperatorsdk/annotation/Sample.java @@ -0,0 +1,29 @@ +package io.javaoperatorsdk.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to mark integration tests as samples for documentation generation. Tests annotated + * with @Sample will be included in the generated samples.md documentation file. + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +public @interface Sample { + + /** + * A short title describing the test sample. + * + * @return the short title + */ + String tldr(); + + /** + * A detailed description of what the test does and demonstrates. + * + * @return the detailed description + */ + String description(); +} diff --git a/test-index-processor/src/main/java/io/javaoperatorsdk/processor/SampleProcessor.java b/test-index-processor/src/main/java/io/javaoperatorsdk/processor/SampleProcessor.java new file mode 100644 index 0000000000..d59e7bf687 --- /dev/null +++ b/test-index-processor/src/main/java/io/javaoperatorsdk/processor/SampleProcessor.java @@ -0,0 +1,143 @@ +package io.javaoperatorsdk.processor; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; +import javax.tools.FileObject; +import javax.tools.StandardLocation; + +import io.javaoperatorsdk.annotation.Sample; + +/** + * Annotation processor that generates a samples.md file containing documentation for all + * integration tests annotated with @Sample. + */ +@SupportedAnnotationTypes("io.javaoperatorsdk.annotation.Sample") +@SupportedSourceVersion(SourceVersion.RELEASE_17) +public class SampleProcessor extends AbstractProcessor { + + private final List samples = new ArrayList<>(); + private boolean processed = false; + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + if (processed) { + return true; + } + + // Collect all @Sample annotated elements + for (Element element : roundEnv.getElementsAnnotatedWith(Sample.class)) { + if (element instanceof TypeElement) { + TypeElement typeElement = (TypeElement) element; + Sample sample = element.getAnnotation(Sample.class); + + String className = typeElement.getQualifiedName().toString(); + String simpleName = typeElement.getSimpleName().toString(); + String tldr = sample.tldr(); + String description = sample.description(); + + samples.add(new SampleInfo(className, simpleName, tldr, description)); + } + } + + // Generate the markdown file in the last round + if (roundEnv.processingOver() && !samples.isEmpty()) { + generateMarkdownFile(); + processed = true; + } + + return true; + } + + private void generateMarkdownFile() { + try { + // Sort samples by class name for consistent ordering + samples.sort(Comparator.comparing(s -> s.className)); + + // Create the samples.md file in the source output location + FileObject file = + processingEnv.getFiler().createResource(StandardLocation.SOURCE_OUTPUT, "", "samples.md"); + + try (BufferedWriter writer = new BufferedWriter(file.openWriter())) { + writer.write("---\n"); + writer.write("title: Integration Test Index\n"); + writer.write("weight: 105\n"); + writer.write("---\n\n"); + writer.write( + "This document provides an index of all integration tests annotated with @Sample.\n\n" + + "These server also as sample for various use cases. " + + "Your are encouraged to improve both the tests and/or descriptions.\n\n"); + + // Generate table of contents + writer.write("## Contents\n\n"); + for (SampleInfo sample : samples) { + String anchor = sample.simpleName.toLowerCase(); + writer.write("- [" + sample.tldr + "](#" + anchor + ")\n"); + } + writer.write("\n---\n\n"); + + // Generate individual test sections + for (SampleInfo sample : samples) { + writer.write("## " + sample.simpleName + "\n\n"); + writer.write("**" + sample.tldr + "**\n\n"); + writer.write(sample.description + "\n\n"); + writer.write("**Package:** " + getGitHubPackageLink(sample.className) + "\n\n"); + writer.write("---\n\n"); + } + } + + processingEnv + .getMessager() + .printMessage( + Diagnostic.Kind.NOTE, "Generated samples.md with " + samples.size() + " samples"); + } catch (IOException e) { + processingEnv + .getMessager() + .printMessage(Diagnostic.Kind.ERROR, "Failed to generate samples.md: " + e.getMessage()); + } + } + + private String getGitHubPackageLink(String className) { + // Extract package name by removing the simple class name + int lastDot = className.lastIndexOf('.'); + if (lastDot == -1) { + return "[root package]"; + } + + String packageName = className.substring(0, lastDot); + String packagePath = packageName.replace('.', '/'); + + // GitHub repository base URL + String baseUrl = "https://github.com/operator-framework/java-operator-sdk/tree/main"; + String sourcePath = "operator-framework/src/test/java"; + + String githubUrl = baseUrl + "/" + sourcePath + "/" + packagePath; + return "[" + packageName + "](" + githubUrl + ")"; + } + + private static class SampleInfo { + final String className; + final String simpleName; + final String tldr; + final String description; + + SampleInfo(String className, String simpleName, String tldr, String description) { + this.className = className; + this.simpleName = simpleName; + this.tldr = tldr; + this.description = description; + } + } +} diff --git a/test-index-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/test-index-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 0000000000..a9fb2eb851 --- /dev/null +++ b/test-index-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1 @@ +io.javaoperatorsdk.processor.SampleProcessor