Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@
*/
package io.javaoperatorsdk.operator.processing.event.source.cache;

import io.javaoperatorsdk.operator.api.Public;

import com.github.benmanes.caffeine.cache.Cache;

/** Caffeine cache wrapper to be used in a {@link BoundedItemStore} */
@Public
public class CaffeineBoundedCache<K, R> implements BoundedCache<K, R> {

private final Cache<K, R> cache;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.javaoperatorsdk.operator.api.Public;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
Expand All @@ -41,6 +42,7 @@
* resources are usually reconciled too, they might need to be fetched and populated to the cache,
* and will remain there for some time, for subsequent reconciliations.
*/
@Public
public class CaffeineBoundedItemStores {

private CaffeineBoundedItemStores() {}
Expand Down
1 change: 1 addition & 0 deletions docs/content/en/docs/documentation/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ This section contains detailed documentation for all Java Operator SDK features
- **[Accessing Resources in Caches](working-with-es-caches/)** - How to access resources in caches
- **[Operations](operations/)** - Helm chart, metrics, logging, configurations, leader election
- **[Other Features](features/)** - Additional capabilities and integrations
- **[Public API and Versioning](api-stability/)** - What is covered by semantic versioning

Each guide includes practical examples and best practices to help you build robust, production-ready operators.
70 changes: 70 additions & 0 deletions docs/content/en/docs/documentation/api-stability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
title: Public API and versioning
weight: 58
---

The Java Operator SDK (JOSDK) follows [semantic versioning](https://semver.org/). The
guarantees that come with semantic versioning &mdash; most importantly, that no breaking changes are
introduced in minor or patch releases &mdash; apply to the **public API** only. To make it explicit
which parts of the codebase are part of that public API, JOSDK uses a small set of source-level
annotations.

## `@Public`

Types that are part of the public API are marked with
[`@Public`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/Public.java).
Such API is subject to semantic versioning: it will not be changed in a backwards-incompatible way
within a major version.

Marking a type with `@Public` is enough &mdash; it means that all of its public members (methods and
fields) are part of the public API. There is no need to annotate the individual members.

```java
@Public
public interface Reconciler<P extends HasMetadata> {
// ...
}
```

## `@Internal`

Everything that is **not** marked with `@Public` should be considered internal: it may change or be
removed at any time, in any release, and should not be relied upon by end users.

The
[`@Internal`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/Internal.java)
annotation is used as an *exception marker*: it flags a member (or nested type) of an otherwise
`@Public` type as internal, signalling that the member is not covered by the semantic-versioning
guarantees even though the enclosing type is. It is not meant to be put on every internal class
&mdash; internal classes are simply left without the `@Public` annotation.

A typical case is a public type that has to expose framework callbacks (for example, methods invoked
by the underlying informer or by the framework's eventing machinery) which operator authors are never
expected to call themselves:

```java
@Public
public class InformerEventSource<R extends HasMetadata, P extends HasMetadata> // ...
implements ResourceEventHandler<R> {

@Override
@Internal // called by the informer, not by end users
public void onAdd(R newResource) {
// ...
}
}
```

## `@Experimental`

[`@Experimental`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/Experimental.java)
marks API that is available and intended to be maintained, but is not yet stable. Such API may still
change between releases &mdash; usually based on user feedback &mdash; even though it is technically
reachable. Experimental API is therefore **not** subject to the semantic-versioning guarantees and is
not marked `@Public`.

## Note for contributors

When you add new API that is meant to be used by operator authors, annotate the type with `@Public`
so that it becomes part of the versioning contract. If a public type exposes a member that is only
there for internal use, mark that member with `@Internal`. Leave purely internal classes unannotated.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.OperatorException;
import io.javaoperatorsdk.operator.api.Public;
import io.javaoperatorsdk.operator.api.monitoring.Metrics;
import io.javaoperatorsdk.operator.api.reconciler.Constants;
import io.javaoperatorsdk.operator.api.reconciler.RetryInfo;
Expand All @@ -43,6 +44,7 @@
* @deprecated Use {@link MicrometerMetricsV2} instead
*/
@Deprecated(forRemoval = true)
@Public
public class MicrometerMetrics implements Metrics {

private static final String PREFIX = "operator.sdk.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.OperatorException;
import io.javaoperatorsdk.operator.api.Public;
import io.javaoperatorsdk.operator.api.monitoring.Metrics;
import io.javaoperatorsdk.operator.api.reconciler.Constants;
import io.javaoperatorsdk.operator.api.reconciler.RetryInfo;
Expand All @@ -39,6 +40,7 @@
/**
* @since 5.3.0
*/
@Public
public class MicrometerMetricsV2 implements Metrics {

private static final String CONTROLLER_NAME = "controller.name";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
import java.util.Map.Entry;
import java.util.stream.Collectors;

import io.javaoperatorsdk.operator.api.Public;

@Public
public class AggregatedOperatorException extends OperatorException {

private final Map<String, Exception> causes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
*/
package io.javaoperatorsdk.operator;

import io.javaoperatorsdk.operator.api.Public;

@Public
public class MissingCRDException extends OperatorException {
private final String crdName;
private final String specVersion;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.Version;
import io.javaoperatorsdk.operator.api.Public;
import io.javaoperatorsdk.operator.api.config.ConfigurationService;
import io.javaoperatorsdk.operator.api.config.ConfigurationServiceOverrider;
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
Expand All @@ -37,6 +38,7 @@
import io.javaoperatorsdk.operator.processing.LifecycleAware;

@SuppressWarnings("rawtypes")
@Public
public class Operator implements LifecycleAware {
private static final Logger log = LoggerFactory.getLogger(Operator.class);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
*/
package io.javaoperatorsdk.operator;

import io.javaoperatorsdk.operator.api.Public;

@Public
public class OperatorException extends RuntimeException {

public OperatorException() {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
package io.javaoperatorsdk.operator;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.api.Public;
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.config.NamespaceChangeable;
import io.javaoperatorsdk.operator.health.ControllerHealthInfo;

@Public
public interface RegisteredController<P extends HasMetadata> extends NamespaceChangeable {

ControllerConfiguration<P> getConfiguration();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.slf4j.LoggerFactory;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.api.Public;
import io.javaoperatorsdk.operator.health.EventSourceHealthIndicator;
import io.javaoperatorsdk.operator.health.InformerWrappingEventSourceHealthIndicator;
import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource;
Expand All @@ -30,6 +31,7 @@
* check that.
*/
@SuppressWarnings("rawtypes")
@Public
public class RuntimeInfo {

private static final Logger log = LoggerFactory.getLogger(RuntimeInfo.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.javaoperatorsdk.operator.api.reconciler;
package io.javaoperatorsdk.operator.api;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Marks experimental features.
* Marks experimental all API.
*
* <p>Experimental features are not yet stable and may change in future releases. Usually based on
* the feedback of the users.
* <p>Experimental API are not yet stable and may change in future releases. Usually based on the
* feedback of the users.
*/
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD, ElementType.PACKAGE})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright Java Operator SDK Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.javaoperatorsdk.operator.api;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Marks API as internal. Such API should not be used by end users and/or are not subject of
* semantic versioning.
*
* <p>The intention with this annotation is not to mark all internal APIs, just make exceptions for
* classes that are marked as {@link Public}.
*/
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD, ElementType.PACKAGE})
public @interface Internal {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright Java Operator SDK Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.javaoperatorsdk.operator.api;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Marks API as public. Such API is subject of semantic versioning. If a class is marked, it means
* all it's public methods are part of the public API.
*/
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD, ElementType.PACKAGE})
public @interface Public {}
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.Public;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;

/**
* An abstract implementation of {@link ConfigurationService} meant to ease custom implementations
*/
@SuppressWarnings("rawtypes")
@Public
public class AbstractConfigurationService implements ConfigurationService {
private final Map<String, ControllerConfiguration> configurations = new ConcurrentHashMap<>();
private final Version version;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@

import java.lang.annotation.Annotation;

import io.javaoperatorsdk.operator.api.Public;

@Public
public interface AnnotationConfigurable<A extends Annotation> {
void initFrom(A configuration);
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.Public;
import io.javaoperatorsdk.operator.api.config.Utils.Configurator;
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceConfigurationResolver;
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
Expand All @@ -51,6 +52,7 @@
* use {@link AbstractConfigurationService} instead as a base for your {@code ConfigurationService}
* implementation.
*/
@Public
public class BaseConfigurationService extends AbstractConfigurationService {

private static final Logger logger = LoggerFactory.getLogger(BaseConfigurationService.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
package io.javaoperatorsdk.operator.api.config;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.api.Public;

@Public
public interface Cloner {

<R extends HasMetadata> R clone(R object);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
import io.fabric8.kubernetes.client.utils.KubernetesSerialization;
import io.javaoperatorsdk.operator.api.Public;
import io.javaoperatorsdk.operator.api.monitoring.Metrics;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
Expand All @@ -45,6 +46,7 @@
import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource;

/** An interface from which to retrieve configuration information. */
@Public
public interface ConfigurationService {

Logger log = LoggerFactory.getLogger(ConfigurationService.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.javaoperatorsdk.operator.Operator;
import io.javaoperatorsdk.operator.api.Public;
import io.javaoperatorsdk.operator.api.monitoring.Metrics;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResourceFactory;

@SuppressWarnings({"unused", "UnusedReturnValue"})
@Public
public class ConfigurationServiceOverrider {

private static final Logger log = LoggerFactory.getLogger(ConfigurationServiceOverrider.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.Public;
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
import io.javaoperatorsdk.operator.api.config.workflow.WorkflowSpec;
import io.javaoperatorsdk.operator.api.reconciler.MaxReconciliationInterval;
Expand All @@ -29,6 +30,7 @@
import io.javaoperatorsdk.operator.processing.retry.GenericRetry;
import io.javaoperatorsdk.operator.processing.retry.Retry;

@Public
public interface ControllerConfiguration<P extends HasMetadata> extends Informable<P> {

@SuppressWarnings("rawtypes")
Expand Down
Loading
Loading