CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-fabric8--kubernetes-server-mock

A JUnit 5 testing library that provides a Kubernetes mock server for testing Kubernetes client applications

Pending
Overview
Eval results
Files

custom-resources.mddocs/

Custom Resource Support

Support for Custom Resource Definitions and Custom Resources with automatic API discovery and full CRUD operations.

Capabilities

CustomResourceAware Interface

Interface for components that support custom resource operations.

/**
 * Interface for components that support custom resource operations
 * Enables registration and handling of custom resource definitions
 */
public interface CustomResourceAware {
    /**
     * Register a custom resource definition context
     * Enables the mock server to handle requests for this custom resource type
     * @param rdc CustomResourceDefinitionContext defining the custom resource
     */
    void expectCustomResource(CustomResourceDefinitionContext rdc);
}

CustomResourceDefinitionProcessor

Processes custom resource definitions and handles API discovery for custom resources.

/**
 * Processes custom resource definitions and handles API metadata
 * Manages CRD registration and API resource list generation
 */
public class CustomResourceDefinitionProcessor {
    
    /**
     * Add a custom resource definition context
     * Registers the CRD for API discovery and resource handling
     * @param context CustomResourceDefinitionContext to register
     */
    public void addCrdContext(CustomResourceDefinitionContext context);
    
    /**
     * Get API resources for a given path
     * Returns APIResourceList for API group/version endpoints
     * @param path API path (e.g., "/apis/example.com/v1")
     * @return JSON string of APIResourceList or null if not applicable
     */
    public String getApiResources(String path);
    
    /**
     * Check if status subresource is enabled for a resource
     * @param pathValues Map containing resource path information
     * @return true if status subresource is enabled
     */
    public boolean isStatusSubresourceEnabledForResource(Map<String, String> pathValues);
    
    /**
     * Process CRD-related events
     * Handles CRD creation, updates, and deletion
     * @param path Request path
     * @param crdString CRD resource JSON string
     * @param delete Whether the CRD was deleted
     */
    public void process(String path, String crdString, boolean delete);
    
    /**
     * Get CRD context by API coordinates
     * @param api API group name
     * @param version API version
     * @param plural Resource plural name
     * @return Optional containing CRD context if found
     */
    public Optional<CustomResourceDefinitionContext> getCrdContext(String api, String version, String plural);
    
    /**
     * Find CRD by kind
     * @param api API group name
     * @param version API version
     * @param kind Resource kind name
     * @return Optional containing CRD context if found
     */
    public Optional<CustomResourceDefinitionContext> findCrd(String api, String version, String kind);
    
    /**
     * Remove a CRD context from the processor
     * @param context CRD context to remove
     */
    public void removeCrdContext(CustomResourceDefinitionContext context);
    
    /**
     * Reset the processor state
     * Clears all registered custom resource definitions
     */
    public void reset();
}

Custom Resource Registration

Methods for registering custom resources with the mock server.

Basic Registration:

@EnableKubernetesMockClient(crud = true)
class CustomResourceTest {
    KubernetesMockServer server;
    KubernetesClient client;
    
    @Test
    void testCustomResourceRegistration() {
        // Define custom resource context
        CustomResourceDefinitionContext crdContext = new CustomResourceDefinitionContext.Builder()
            .withGroup("example.com")
            .withVersion("v1")
            .withKind("MyResource")
            .withPlural("myresources")
            .withNamespaceScoped(true)
            .build();
        
        // Register with server
        server.expectCustomResource(crdContext);
        
        // Create generic client for custom resource
        GenericKubernetesResource customResource = new GenericKubernetesResourceBuilder()
            .withNewMetadata()
                .withName("my-custom-resource")
                .withNamespace("default")
            .endMetadata()
            .withApiVersion("example.com/v1")
            .withKind("MyResource")
            .addToAdditionalProperties("spec", Map.of("field1", "value1"))
            .build();
        
        // Use generic client
        Resource<GenericKubernetesResource> resource = client
            .genericKubernetesResources(crdContext)
            .inNamespace("default")
            .resource(customResource);
            
        GenericKubernetesResource created = resource.create();
        assertEquals("my-custom-resource", created.getMetadata().getName());
    }
}

Advanced Custom Resource Features:

@Test
void testCustomResourceWithStatus() {
    // CRD with status subresource enabled
    CustomResourceDefinitionContext crdContext = new CustomResourceDefinitionContext.Builder()
        .withGroup("example.com")
        .withVersion("v1")
        .withKind("MyApp")
        .withPlural("myapps")
        .withNamespaceScoped(true)
        .withStatusSubresource(true) // Enable status subresource
        .build();
    
    server.expectCustomResource(crdContext);
    
    // Create custom resource
    GenericKubernetesResource app = new GenericKubernetesResourceBuilder()
        .withNewMetadata()
            .withName("my-app")
            .withNamespace("default")
        .endMetadata()
        .withApiVersion("example.com/v1")
        .withKind("MyApp")
        .addToAdditionalProperties("spec", Map.of(
            "replicas", 3,
            "image", "nginx:latest"
        ))
        .build();
    
    GenericKubernetesResource created = client
        .genericKubernetesResources(crdContext)
        .inNamespace("default")
        .resource(app)
        .create();
    
    // Update status subresource
    Map<String, Object> status = Map.of(
        "readyReplicas", 3,
        "phase", "Running"
    );
    created.setAdditionalProperty("status", status);
    
    GenericKubernetesResource updated = client
        .genericKubernetesResources(crdContext)
        .inNamespace("default")
        .withName("my-app")
        .updateStatus(created);
    
    assertEquals("Running", 
        ((Map<String, Object>) updated.getAdditionalProperties().get("status")).get("phase"));
}

API Discovery Integration

Custom resources integrate with Kubernetes API discovery mechanisms.

@Test
void testCustomResourceApiDiscovery() {
    // Register multiple custom resources in same API group
    CustomResourceDefinitionContext app = new CustomResourceDefinitionContext.Builder()
        .withGroup("example.com")
        .withVersion("v1")
        .withKind("MyApp")
        .withPlural("myapps")
        .withNamespaceScoped(true)
        .build();
    
    CustomResourceDefinitionContext config = new CustomResourceDefinitionContext.Builder()
        .withGroup("example.com")
        .withVersion("v1")
        .withKind("MyConfig")
        .withPlural("myconfigs")
        .withNamespaceScoped(false) // Cluster-scoped
        .build();
    
    server.expectCustomResource(app);
    server.expectCustomResource(config);
    
    // API discovery should work
    APIResourceList resources = client.apiextensions().apiResources("example.com/v1");
    assertNotNull(resources);
    
    // Should contain both custom resources
    List<String> resourceNames = resources.getResources().stream()
        .map(APIResource::getName)
        .collect(Collectors.toList());
    
    assertTrue(resourceNames.contains("myapps"));
    assertTrue(resourceNames.contains("myconfigs"));
    
    // Check namespace scoping
    APIResource appResource = resources.getResources().stream()
        .filter(r -> "myapps".equals(r.getName()))
        .findFirst()
        .orElseThrow();
    assertTrue(appResource.getNamespaced());
    
    APIResource configResource = resources.getResources().stream()
        .filter(r -> "myconfigs".equals(r.getName()))
        .findFirst()
        .orElseThrow();
    assertFalse(configResource.getNamespaced());
}

Custom Resource Definition Management

Handling of CRD resources themselves within the mock server.

@Test
void testCrdManagement() {
    // Create a CRD resource
    CustomResourceDefinition crd = new CustomResourceDefinitionBuilder()
        .withNewMetadata()
            .withName("myapps.example.com")
        .endMetadata()
        .withNewSpec()
            .withGroup("example.com")
            .withNewNames()
                .withKind("MyApp")
                .withPlural("myapps")
                .withSingular("myapp")
            .endNames()
            .withScope("Namespaced")
            .addNewVersion()
                .withName("v1")
                .withServed(true)
                .withStorage(true)
                .withNewSchema()
                    .withNewOpenAPIV3Schema()
                        .withType("object")
                        .addToProperties("spec", new JSONSchemaPropsBuilder()
                            .withType("object")
                            .addToProperties("replicas", new JSONSchemaPropsBuilder()
                                .withType("integer")
                                .build())
                            .build())
                    .endOpenAPIV3Schema()
                .endSchema()
            .endVersion()
        .endSpec()
        .build();
    
    // Create CRD in the cluster
    CustomResourceDefinition created = client.apiextensions().v1()
        .customResourceDefinitions()
        .resource(crd)
        .create();
    
    assertNotNull(created);
    assertEquals("myapps.example.com", created.getMetadata().getName());
    
    // The CRD should automatically enable the custom resource
    // (In CRUD mode, CRDs are automatically processed)
    
    // Now we can create instances of the custom resource
    GenericKubernetesResource myApp = new GenericKubernetesResourceBuilder()
        .withNewMetadata()
            .withName("test-app")
            .withNamespace("default")
        .endMetadata()
        .withApiVersion("example.com/v1")
        .withKind("MyApp")
        .addToAdditionalProperties("spec", Map.of("replicas", 5))
        .build();
    
    // Use the automatically registered custom resource
    CustomResourceDefinitionContext context = CustomResourceDefinitionContext.fromCrd(created);
    GenericKubernetesResource createdApp = client
        .genericKubernetesResources(context)
        .inNamespace("default")
        .resource(myApp)
        .create();
    
    assertEquals("test-app", createdApp.getMetadata().getName());
    assertEquals(5, ((Map<String, Object>) createdApp.getAdditionalProperties().get("spec")).get("replicas"));
}

Cluster and Namespace Scoped Resources

Support for both cluster-scoped and namespace-scoped custom resources.

@Test
void testClusterScopedCustomResource() {
    // Define cluster-scoped custom resource
    CustomResourceDefinitionContext globalConfig = new CustomResourceDefinitionContext.Builder()
        .withGroup("config.example.com")
        .withVersion("v1")
        .withKind("GlobalConfig")
        .withPlural("globalconfigs")
        .withNamespaceScoped(false) // Cluster-scoped
        .build();
    
    server.expectCustomResource(globalConfig);
    
    // Create cluster-scoped resource (no namespace)
    GenericKubernetesResource config = new GenericKubernetesResourceBuilder()
        .withNewMetadata()
            .withName("cluster-config")
            // No namespace for cluster-scoped resources
        .endMetadata()
        .withApiVersion("config.example.com/v1")
        .withKind("GlobalConfig")
        .addToAdditionalProperties("data", Map.of(
            "globalSetting", "enabled",
            "maxConnections", 1000
        ))
        .build();
    
    // Use without namespace
    GenericKubernetesResource created = client
        .genericKubernetesResources(globalConfig)
        .resource(config)
        .create();
    
    assertEquals("cluster-config", created.getMetadata().getName());
    assertNull(created.getMetadata().getNamespace());
    
    // List cluster-scoped resources
    List<GenericKubernetesResource> configs = client
        .genericKubernetesResources(globalConfig)
        .list()
        .getItems();
    
    assertEquals(1, configs.size());
}

@Test
void testNamespaceScopedCustomResource() {
    // Define namespace-scoped custom resource
    CustomResourceDefinitionContext appConfig = new CustomResourceDefinitionContext.Builder()
        .withGroup("config.example.com")
        .withVersion("v1")
        .withKind("AppConfig")
        .withPlural("appconfigs")
        .withNamespaceScoped(true) // Namespace-scoped
        .build();
    
    server.expectCustomResource(appConfig);
    
    // Create namespace-scoped resources in different namespaces
    GenericKubernetesResource config1 = new GenericKubernetesResourceBuilder()
        .withNewMetadata()
            .withName("app-config")
            .withNamespace("namespace1")
        .endMetadata()
        .withApiVersion("config.example.com/v1")
        .withKind("AppConfig")
        .build();
    
    GenericKubernetesResource config2 = new GenericKubernetesResourceBuilder()
        .withNewMetadata()
            .withName("app-config")
            .withNamespace("namespace2")
        .endMetadata()
        .withApiVersion("config.example.com/v1")
        .withKind("AppConfig")
        .build();
    
    // Create in different namespaces
    client.genericKubernetesResources(appConfig).inNamespace("namespace1").resource(config1).create();
    client.genericKubernetesResources(appConfig).inNamespace("namespace2").resource(config2).create();
    
    // List per namespace
    List<GenericKubernetesResource> ns1Configs = client
        .genericKubernetesResources(appConfig)
        .inNamespace("namespace1")
        .list()
        .getItems();
    assertEquals(1, ns1Configs.size());
    
    // List across all namespaces
    List<GenericKubernetesResource> allConfigs = client
        .genericKubernetesResources(appConfig)
        .inAnyNamespace()
        .list()
        .getItems();
    assertEquals(2, allConfigs.size());
}

Install with Tessl CLI

npx tessl i tessl/maven-io-fabric8--kubernetes-server-mock

docs

attribute-extraction.md

crud-handlers.md

crud-operations.md

custom-resources.md

index.md

junit-integration.md

mock-server-management.md

websocket-operations.md

tile.json