Java client for Kubernetes and OpenShift providing access to the full Kubernetes & OpenShift REST APIs via a fluent DSL
—
The Fabric8 Kubernetes Client provides comprehensive support for Custom Resource Definitions (CRDs) and custom resources. This includes type-safe operations on custom resources, dynamic resource handling for unknown types, and utilities for working with CRDs.
Base class for creating type-safe custom resource implementations.
public abstract class CustomResource<T, S> implements HasMetadata {
// Spec and Status management
public T getSpec();
public void setSpec(T spec);
public S getStatus();
public void setStatus(S status);
// Metadata implementation from HasMetadata
public ObjectMeta getMetadata();
public void setMetadata(ObjectMeta metadata);
public String getKind();
public void setKind(String kind);
public String getApiVersion();
public void setApiVersion(String apiVersion);
}Operations on strongly-typed custom resources using the generic resources method.
public interface KubernetesClient {
<T extends HasMetadata> MixedOperation<T, KubernetesResourceList<T>, Resource<T>>
resources(Class<T> resourceType);
<T extends HasMetadata> MixedOperation<T, KubernetesResourceList<T>, Resource<T>>
resources(Class<T> resourceType, Class<? extends KubernetesResourceList<T>> listClass);
}Operations on custom resources using GenericKubernetesResource for dynamic handling.
public interface KubernetesClient {
MixedOperation<GenericKubernetesResource, GenericKubernetesResourceList, Resource<GenericKubernetesResource>>
genericKubernetesResources(String apiVersion, String kind);
MixedOperation<GenericKubernetesResource, GenericKubernetesResourceList, Resource<GenericKubernetesResource>>
genericKubernetesResources(ResourceDefinitionContext context);
}Context information for custom resource definitions.
public class ResourceDefinitionContext {
// Factory methods
public static ResourceDefinitionContext fromResourceType(Class<? extends HasMetadata> resourceType);
public static ResourceDefinitionContext fromCrd(CustomResourceDefinition crd);
// Properties
public String getGroup();
public String getVersion();
public String getKind();
public String getPlural();
public String getSingular();
public boolean isNamespaceScoped();
// Builder methods
public static ResourceDefinitionContextBuilder builder();
}Operations for managing Custom Resource Definitions.
public interface ApiextensionsAPIGroupDSL {
// V1 API (preferred)
V1ApiextensionsAPIGroupDSL v1();
// Default methods (v1)
NonNamespaceOperation<CustomResourceDefinition, CustomResourceDefinitionList, Resource<CustomResourceDefinition>> customResourceDefinitions();
}
public interface V1ApiextensionsAPIGroupDSL {
NonNamespaceOperation<CustomResourceDefinition, CustomResourceDefinitionList, Resource<CustomResourceDefinition>> customResourceDefinitions();
}
// Access via client
public interface KubernetesClient {
ApiextensionsAPIGroupDSL apiextensions();
}// Define the spec class
public class DatabaseSpec {
private String version;
private int replicas;
private String storageSize;
// Getters and setters
public String getVersion() { return version; }
public void setVersion(String version) { this.version = version; }
public int getReplicas() { return replicas; }
public void setReplicas(int replicas) { this.replicas = replicas; }
public String getStorageSize() { return storageSize; }
public void setStorageSize(String storageSize) { this.storageSize = storageSize; }
}
// Define the status class
public class DatabaseStatus {
private String phase;
private String message;
private List<String> conditions;
// Getters and setters
public String getPhase() { return phase; }
public void setPhase(String phase) { this.phase = phase; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public List<String> getConditions() { return conditions; }
public void setConditions(List<String> conditions) { this.conditions = conditions; }
}
// Define the custom resource class
@Group("example.com")
@Version("v1")
@Kind("Database")
@Plural("databases")
@Singular("database")
public class Database extends CustomResource<DatabaseSpec, DatabaseStatus> {
// The CustomResource base class provides all necessary methods
}// Create CRD for the Database custom resource
CustomResourceDefinition crd = client.apiextensions().v1().customResourceDefinitions()
.create(new CustomResourceDefinitionBuilder()
.withNewMetadata()
.withName("databases.example.com")
.endMetadata()
.withNewSpec()
.withGroup("example.com")
.withScope("Namespaced")
.addNewVersion()
.withName("v1")
.withServed(true)
.withStorage(true)
.withNewSchema()
.withNewOpenAPIV3Schema()
.withType("object")
.addToProperties("spec", new JSONSchemaPropsBuilder()
.withType("object")
.addToProperties("version", new JSONSchemaPropsBuilder()
.withType("string")
.build())
.addToProperties("replicas", new JSONSchemaPropsBuilder()
.withType("integer")
.withMinimum(1.0)
.build())
.addToProperties("storageSize", new JSONSchemaPropsBuilder()
.withType("string")
.build())
.build())
.addToProperties("status", new JSONSchemaPropsBuilder()
.withType("object")
.addToProperties("phase", new JSONSchemaPropsBuilder()
.withType("string")
.build())
.addToProperties("message", new JSONSchemaPropsBuilder()
.withType("string")
.build())
.build())
.build()
.endSchema()
.endVersion()
.withNewNames()
.withKind("Database")
.withPlural("databases")
.withSingular("database")
.endNames()
.endSpec()
.build());
System.out.println("Created CRD: " + crd.getMetadata().getName());// Create a Database custom resource instance
Database database = new Database();
database.setMetadata(new ObjectMetaBuilder()
.withName("my-database")
.withNamespace("production")
.build());
DatabaseSpec spec = new DatabaseSpec();
spec.setVersion("13.4");
spec.setReplicas(3);
spec.setStorageSize("100Gi");
database.setSpec(spec);
// Create the custom resource
Database created = client.resources(Database.class).create(database);
System.out.println("Created database: " + created.getMetadata().getName());
// List all Database resources
KubernetesResourceList<Database> databases = client.resources(Database.class).list();
for (Database db : databases.getItems()) {
System.out.println("Database: " + db.getMetadata().getName() +
" version: " + db.getSpec().getVersion());
}
// Update custom resource
Database updated = client.resources(Database.class)
.withName("my-database")
.edit(db -> {
db.getSpec().setReplicas(5);
return db;
});
// Update status subresource
client.resources(Database.class)
.withName("my-database")
.editStatus(db -> {
DatabaseStatus status = new DatabaseStatus();
status.setPhase("Running");
status.setMessage("Database is healthy");
db.setStatus(status);
return db;
});
// Watch for changes
Watch watch = client.resources(Database.class).watch(new Watcher<Database>() {
@Override
public void eventReceived(Action action, Database database) {
System.out.println(action + ": " + database.getMetadata().getName());
if (database.getStatus() != null) {
System.out.println("Status: " + database.getStatus().getPhase());
}
}
@Override
public void onClose(WatcherException cause) {
System.out.println("Watch closed: " + cause);
}
});When you don't have a typed class or need to work with arbitrary custom resources:
// Using apiVersion and kind
MixedOperation<GenericKubernetesResource, GenericKubernetesResourceList, Resource<GenericKubernetesResource>>
genericOp = client.genericKubernetesResources("example.com/v1", "Database");
// Create a generic custom resource
GenericKubernetesResource genericDatabase = new GenericKubernetesResource();
genericDatabase.setApiVersion("example.com/v1");
genericDatabase.setKind("Database");
genericDatabase.setMetadata(new ObjectMetaBuilder()
.withName("generic-database")
.withNamespace("default")
.build());
// Set spec using the additional properties map
Map<String, Object> spec = new HashMap<>();
spec.put("version", "14.1");
spec.put("replicas", 2);
spec.put("storageSize", "50Gi");
genericDatabase.setAdditionalProperty("spec", spec);
// Create the resource
GenericKubernetesResource created = genericOp.create(genericDatabase);
// Access spec properties
Map<String, Object> createdSpec = (Map<String, Object>) created.getAdditionalProperties().get("spec");
String version = (String) createdSpec.get("version");
Integer replicas = (Integer) createdSpec.get("replicas");
System.out.println("Created database version: " + version + " with " + replicas + " replicas");
// List generic resources
GenericKubernetesResourceList list = genericOp.list();
for (GenericKubernetesResource resource : list.getItems()) {
System.out.println("Found resource: " + resource.getMetadata().getName());
}For more explicit resource definition:
// Create ResourceDefinitionContext
ResourceDefinitionContext context = new ResourceDefinitionContextBuilder()
.withGroup("example.com")
.withVersion("v1")
.withKind("Database")
.withPlural("databases")
.withNamespaceScoped(true)
.build();
// Use with generic resources
MixedOperation<GenericKubernetesResource, GenericKubernetesResourceList, Resource<GenericKubernetesResource>>
contextOp = client.genericKubernetesResources(context);
// All operations work the same as with apiVersion/kind approach
GenericKubernetesResourceList databases = contextOp.inNamespace("production").list();Create ResourceDefinitionContext from an existing CRD:
// Get existing CRD
CustomResourceDefinition crd = client.apiextensions().v1().customResourceDefinitions()
.withName("databases.example.com")
.get();
if (crd != null) {
// Create context from CRD
ResourceDefinitionContext context = ResourceDefinitionContext.fromCrd(crd);
// Use for operations
MixedOperation<GenericKubernetesResource, GenericKubernetesResourceList, Resource<GenericKubernetesResource>>
crdOp = client.genericKubernetesResources(context);
GenericKubernetesResourceList resources = crdOp.list();
System.out.println("Found " + resources.getItems().size() + " custom resources");
}Custom resources work with the informer framework:
// Create informer for typed custom resource
SharedIndexInformer<Database> informer = client.informers()
.sharedIndexInformerForCustomResource(Database.class, 30 * 1000);
informer.addEventHandler(new ResourceEventHandler<Database>() {
@Override
public void onAdd(Database database) {
System.out.println("Database added: " + database.getMetadata().getName());
}
@Override
public void onUpdate(Database oldDatabase, Database newDatabase) {
System.out.println("Database updated: " + newDatabase.getMetadata().getName());
}
@Override
public void onDelete(Database database, boolean deletedFinalStateUnknown) {
System.out.println("Database deleted: " + database.getMetadata().getName());
}
});
// Start the informer
client.informers().startAllRegisteredInformers();
// Get cached resources
List<Database> cachedDatabases = informer.getIndexer().list();Custom resource operations can encounter specific errors:
try {
Database db = client.resources(Database.class).withName("non-existent").get();
} catch (KubernetesClientException e) {
if (e.getCode() == 404) {
System.out.println("Custom resource not found");
} else if (e.getCode() == 400) {
System.out.println("Invalid custom resource definition: " + e.getMessage());
}
}
// Check if CRD exists before using custom resources
CustomResourceDefinition crd = client.apiextensions().v1().customResourceDefinitions()
.withName("databases.example.com")
.get();
if (crd == null) {
System.out.println("CRD not found - cannot work with Database resources");
} else {
// Safe to use Database resources
KubernetesResourceList<Database> databases = client.resources(Database.class).list();
}Install with Tessl CLI
npx tessl i tessl/maven-io-fabric8--kubernetes-client