CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-fabric8--kubernetes-client

Java client for Kubernetes and OpenShift providing access to the full Kubernetes & OpenShift REST APIs via a fluent DSL

Pending
Overview
Eval results
Files

custom-resources.mddocs/

Custom Resources

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.

Custom Resource Support

CustomResource Base Class

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);
}

Typed Custom Resource Operations

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);
}

Generic Resource Operations

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);
}

ResourceDefinitionContext

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();
}

Custom Resource Definition Operations

CRD Management

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();
}

Usage Examples

Defining a Custom Resource Class

// 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
}

Creating a Custom Resource Definition

// 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());

Working with Typed Custom Resources

// 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);
    }
});

Working with Generic Resources

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());
}

Using ResourceDefinitionContext

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();

Custom Resource from CRD

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 Resource Informers

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();

Error Handling

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

docs

api-groups.md

client-configuration.md

core-resources.md

custom-resources.md

exception-handling.md

index.md

pod-operations.md

utilities.md

watch-informers.md

tile.json