or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

attribute-extraction.mdcrud-handlers.mdcrud-operations.mdcustom-resources.mdindex.mdjunit-integration.mdmock-server-management.mdwebsocket-operations.md

custom-resources.mddocs/

0

# Custom Resource Support

1

2

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

3

4

## Capabilities

5

6

### CustomResourceAware Interface

7

8

Interface for components that support custom resource operations.

9

10

```java { .api }

11

/**

12

* Interface for components that support custom resource operations

13

* Enables registration and handling of custom resource definitions

14

*/

15

public interface CustomResourceAware {

16

/**

17

* Register a custom resource definition context

18

* Enables the mock server to handle requests for this custom resource type

19

* @param rdc CustomResourceDefinitionContext defining the custom resource

20

*/

21

void expectCustomResource(CustomResourceDefinitionContext rdc);

22

}

23

```

24

25

### CustomResourceDefinitionProcessor

26

27

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

28

29

```java { .api }

30

/**

31

* Processes custom resource definitions and handles API metadata

32

* Manages CRD registration and API resource list generation

33

*/

34

public class CustomResourceDefinitionProcessor {

35

36

/**

37

* Add a custom resource definition context

38

* Registers the CRD for API discovery and resource handling

39

* @param context CustomResourceDefinitionContext to register

40

*/

41

public void addCrdContext(CustomResourceDefinitionContext context);

42

43

/**

44

* Get API resources for a given path

45

* Returns APIResourceList for API group/version endpoints

46

* @param path API path (e.g., "/apis/example.com/v1")

47

* @return JSON string of APIResourceList or null if not applicable

48

*/

49

public String getApiResources(String path);

50

51

/**

52

* Check if status subresource is enabled for a resource

53

* @param pathValues Map containing resource path information

54

* @return true if status subresource is enabled

55

*/

56

public boolean isStatusSubresourceEnabledForResource(Map<String, String> pathValues);

57

58

/**

59

* Process CRD-related events

60

* Handles CRD creation, updates, and deletion

61

* @param path Request path

62

* @param crdString CRD resource JSON string

63

* @param delete Whether the CRD was deleted

64

*/

65

public void process(String path, String crdString, boolean delete);

66

67

/**

68

* Get CRD context by API coordinates

69

* @param api API group name

70

* @param version API version

71

* @param plural Resource plural name

72

* @return Optional containing CRD context if found

73

*/

74

public Optional<CustomResourceDefinitionContext> getCrdContext(String api, String version, String plural);

75

76

/**

77

* Find CRD by kind

78

* @param api API group name

79

* @param version API version

80

* @param kind Resource kind name

81

* @return Optional containing CRD context if found

82

*/

83

public Optional<CustomResourceDefinitionContext> findCrd(String api, String version, String kind);

84

85

/**

86

* Remove a CRD context from the processor

87

* @param context CRD context to remove

88

*/

89

public void removeCrdContext(CustomResourceDefinitionContext context);

90

91

/**

92

* Reset the processor state

93

* Clears all registered custom resource definitions

94

*/

95

public void reset();

96

}

97

```

98

99

### Custom Resource Registration

100

101

Methods for registering custom resources with the mock server.

102

103

**Basic Registration:**

104

105

```java

106

@EnableKubernetesMockClient(crud = true)

107

class CustomResourceTest {

108

KubernetesMockServer server;

109

KubernetesClient client;

110

111

@Test

112

void testCustomResourceRegistration() {

113

// Define custom resource context

114

CustomResourceDefinitionContext crdContext = new CustomResourceDefinitionContext.Builder()

115

.withGroup("example.com")

116

.withVersion("v1")

117

.withKind("MyResource")

118

.withPlural("myresources")

119

.withNamespaceScoped(true)

120

.build();

121

122

// Register with server

123

server.expectCustomResource(crdContext);

124

125

// Create generic client for custom resource

126

GenericKubernetesResource customResource = new GenericKubernetesResourceBuilder()

127

.withNewMetadata()

128

.withName("my-custom-resource")

129

.withNamespace("default")

130

.endMetadata()

131

.withApiVersion("example.com/v1")

132

.withKind("MyResource")

133

.addToAdditionalProperties("spec", Map.of("field1", "value1"))

134

.build();

135

136

// Use generic client

137

Resource<GenericKubernetesResource> resource = client

138

.genericKubernetesResources(crdContext)

139

.inNamespace("default")

140

.resource(customResource);

141

142

GenericKubernetesResource created = resource.create();

143

assertEquals("my-custom-resource", created.getMetadata().getName());

144

}

145

}

146

```

147

148

**Advanced Custom Resource Features:**

149

150

```java

151

@Test

152

void testCustomResourceWithStatus() {

153

// CRD with status subresource enabled

154

CustomResourceDefinitionContext crdContext = new CustomResourceDefinitionContext.Builder()

155

.withGroup("example.com")

156

.withVersion("v1")

157

.withKind("MyApp")

158

.withPlural("myapps")

159

.withNamespaceScoped(true)

160

.withStatusSubresource(true) // Enable status subresource

161

.build();

162

163

server.expectCustomResource(crdContext);

164

165

// Create custom resource

166

GenericKubernetesResource app = new GenericKubernetesResourceBuilder()

167

.withNewMetadata()

168

.withName("my-app")

169

.withNamespace("default")

170

.endMetadata()

171

.withApiVersion("example.com/v1")

172

.withKind("MyApp")

173

.addToAdditionalProperties("spec", Map.of(

174

"replicas", 3,

175

"image", "nginx:latest"

176

))

177

.build();

178

179

GenericKubernetesResource created = client

180

.genericKubernetesResources(crdContext)

181

.inNamespace("default")

182

.resource(app)

183

.create();

184

185

// Update status subresource

186

Map<String, Object> status = Map.of(

187

"readyReplicas", 3,

188

"phase", "Running"

189

);

190

created.setAdditionalProperty("status", status);

191

192

GenericKubernetesResource updated = client

193

.genericKubernetesResources(crdContext)

194

.inNamespace("default")

195

.withName("my-app")

196

.updateStatus(created);

197

198

assertEquals("Running",

199

((Map<String, Object>) updated.getAdditionalProperties().get("status")).get("phase"));

200

}

201

```

202

203

### API Discovery Integration

204

205

Custom resources integrate with Kubernetes API discovery mechanisms.

206

207

```java

208

@Test

209

void testCustomResourceApiDiscovery() {

210

// Register multiple custom resources in same API group

211

CustomResourceDefinitionContext app = new CustomResourceDefinitionContext.Builder()

212

.withGroup("example.com")

213

.withVersion("v1")

214

.withKind("MyApp")

215

.withPlural("myapps")

216

.withNamespaceScoped(true)

217

.build();

218

219

CustomResourceDefinitionContext config = new CustomResourceDefinitionContext.Builder()

220

.withGroup("example.com")

221

.withVersion("v1")

222

.withKind("MyConfig")

223

.withPlural("myconfigs")

224

.withNamespaceScoped(false) // Cluster-scoped

225

.build();

226

227

server.expectCustomResource(app);

228

server.expectCustomResource(config);

229

230

// API discovery should work

231

APIResourceList resources = client.apiextensions().apiResources("example.com/v1");

232

assertNotNull(resources);

233

234

// Should contain both custom resources

235

List<String> resourceNames = resources.getResources().stream()

236

.map(APIResource::getName)

237

.collect(Collectors.toList());

238

239

assertTrue(resourceNames.contains("myapps"));

240

assertTrue(resourceNames.contains("myconfigs"));

241

242

// Check namespace scoping

243

APIResource appResource = resources.getResources().stream()

244

.filter(r -> "myapps".equals(r.getName()))

245

.findFirst()

246

.orElseThrow();

247

assertTrue(appResource.getNamespaced());

248

249

APIResource configResource = resources.getResources().stream()

250

.filter(r -> "myconfigs".equals(r.getName()))

251

.findFirst()

252

.orElseThrow();

253

assertFalse(configResource.getNamespaced());

254

}

255

```

256

257

### Custom Resource Definition Management

258

259

Handling of CRD resources themselves within the mock server.

260

261

```java

262

@Test

263

void testCrdManagement() {

264

// Create a CRD resource

265

CustomResourceDefinition crd = new CustomResourceDefinitionBuilder()

266

.withNewMetadata()

267

.withName("myapps.example.com")

268

.endMetadata()

269

.withNewSpec()

270

.withGroup("example.com")

271

.withNewNames()

272

.withKind("MyApp")

273

.withPlural("myapps")

274

.withSingular("myapp")

275

.endNames()

276

.withScope("Namespaced")

277

.addNewVersion()

278

.withName("v1")

279

.withServed(true)

280

.withStorage(true)

281

.withNewSchema()

282

.withNewOpenAPIV3Schema()

283

.withType("object")

284

.addToProperties("spec", new JSONSchemaPropsBuilder()

285

.withType("object")

286

.addToProperties("replicas", new JSONSchemaPropsBuilder()

287

.withType("integer")

288

.build())

289

.build())

290

.endOpenAPIV3Schema()

291

.endSchema()

292

.endVersion()

293

.endSpec()

294

.build();

295

296

// Create CRD in the cluster

297

CustomResourceDefinition created = client.apiextensions().v1()

298

.customResourceDefinitions()

299

.resource(crd)

300

.create();

301

302

assertNotNull(created);

303

assertEquals("myapps.example.com", created.getMetadata().getName());

304

305

// The CRD should automatically enable the custom resource

306

// (In CRUD mode, CRDs are automatically processed)

307

308

// Now we can create instances of the custom resource

309

GenericKubernetesResource myApp = new GenericKubernetesResourceBuilder()

310

.withNewMetadata()

311

.withName("test-app")

312

.withNamespace("default")

313

.endMetadata()

314

.withApiVersion("example.com/v1")

315

.withKind("MyApp")

316

.addToAdditionalProperties("spec", Map.of("replicas", 5))

317

.build();

318

319

// Use the automatically registered custom resource

320

CustomResourceDefinitionContext context = CustomResourceDefinitionContext.fromCrd(created);

321

GenericKubernetesResource createdApp = client

322

.genericKubernetesResources(context)

323

.inNamespace("default")

324

.resource(myApp)

325

.create();

326

327

assertEquals("test-app", createdApp.getMetadata().getName());

328

assertEquals(5, ((Map<String, Object>) createdApp.getAdditionalProperties().get("spec")).get("replicas"));

329

}

330

```

331

332

### Cluster and Namespace Scoped Resources

333

334

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

335

336

```java

337

@Test

338

void testClusterScopedCustomResource() {

339

// Define cluster-scoped custom resource

340

CustomResourceDefinitionContext globalConfig = new CustomResourceDefinitionContext.Builder()

341

.withGroup("config.example.com")

342

.withVersion("v1")

343

.withKind("GlobalConfig")

344

.withPlural("globalconfigs")

345

.withNamespaceScoped(false) // Cluster-scoped

346

.build();

347

348

server.expectCustomResource(globalConfig);

349

350

// Create cluster-scoped resource (no namespace)

351

GenericKubernetesResource config = new GenericKubernetesResourceBuilder()

352

.withNewMetadata()

353

.withName("cluster-config")

354

// No namespace for cluster-scoped resources

355

.endMetadata()

356

.withApiVersion("config.example.com/v1")

357

.withKind("GlobalConfig")

358

.addToAdditionalProperties("data", Map.of(

359

"globalSetting", "enabled",

360

"maxConnections", 1000

361

))

362

.build();

363

364

// Use without namespace

365

GenericKubernetesResource created = client

366

.genericKubernetesResources(globalConfig)

367

.resource(config)

368

.create();

369

370

assertEquals("cluster-config", created.getMetadata().getName());

371

assertNull(created.getMetadata().getNamespace());

372

373

// List cluster-scoped resources

374

List<GenericKubernetesResource> configs = client

375

.genericKubernetesResources(globalConfig)

376

.list()

377

.getItems();

378

379

assertEquals(1, configs.size());

380

}

381

382

@Test

383

void testNamespaceScopedCustomResource() {

384

// Define namespace-scoped custom resource

385

CustomResourceDefinitionContext appConfig = new CustomResourceDefinitionContext.Builder()

386

.withGroup("config.example.com")

387

.withVersion("v1")

388

.withKind("AppConfig")

389

.withPlural("appconfigs")

390

.withNamespaceScoped(true) // Namespace-scoped

391

.build();

392

393

server.expectCustomResource(appConfig);

394

395

// Create namespace-scoped resources in different namespaces

396

GenericKubernetesResource config1 = new GenericKubernetesResourceBuilder()

397

.withNewMetadata()

398

.withName("app-config")

399

.withNamespace("namespace1")

400

.endMetadata()

401

.withApiVersion("config.example.com/v1")

402

.withKind("AppConfig")

403

.build();

404

405

GenericKubernetesResource config2 = new GenericKubernetesResourceBuilder()

406

.withNewMetadata()

407

.withName("app-config")

408

.withNamespace("namespace2")

409

.endMetadata()

410

.withApiVersion("config.example.com/v1")

411

.withKind("AppConfig")

412

.build();

413

414

// Create in different namespaces

415

client.genericKubernetesResources(appConfig).inNamespace("namespace1").resource(config1).create();

416

client.genericKubernetesResources(appConfig).inNamespace("namespace2").resource(config2).create();

417

418

// List per namespace

419

List<GenericKubernetesResource> ns1Configs = client

420

.genericKubernetesResources(appConfig)

421

.inNamespace("namespace1")

422

.list()

423

.getItems();

424

assertEquals(1, ns1Configs.size());

425

426

// List across all namespaces

427

List<GenericKubernetesResource> allConfigs = client

428

.genericKubernetesResources(appConfig)

429

.inAnyNamespace()

430

.list()

431

.getItems();

432

assertEquals(2, allConfigs.size());

433

}

434

```