@appthrust/kest 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,744 @@
1
+ # Example: applies ConfigMap using YAML, file import, and object literal
2
+
3
+ ## Scenario Overview
4
+
5
+ | # | Action | Resource | Status |
6
+ |---|--------|----------|--------|
7
+ | 1 | Create namespace | kest-9hdhj | ✅ |
8
+ | 2 | Apply | ConfigMap/my-config-1 | ✅ |
9
+ | 3 | Apply | ConfigMap/my-config-2 | ✅ |
10
+ | 4 | Apply | ConfigMap/my-config-3 | ✅ |
11
+ | 5 | Assert | ConfigMap/my-config-1 | ✅ |
12
+
13
+ ## Scenario Details
14
+
15
+ ### Given: a new namespace exists
16
+
17
+ **✅ Create Namespace "kest-9hdhj"**
18
+
19
+ ```shell
20
+ kubectl apply -f - <<EOF
21
+ apiVersion: v1
22
+ kind: Namespace
23
+ metadata:
24
+ name: kest-9hdhj
25
+
26
+ EOF
27
+ ```
28
+
29
+ ```text title="stdout"
30
+ namespace/kest-9hdhj created
31
+ ```
32
+
33
+ ### When: I apply ConfigMaps using different formats
34
+
35
+ **✅ Apply ConfigMap "my-config-1" in namespace "kest-9hdhj"**
36
+
37
+ ```shell
38
+ kubectl apply -f - -n kest-9hdhj <<EOF
39
+ apiVersion: v1
40
+ kind: ConfigMap
41
+ metadata:
42
+ name: my-config-1
43
+ data:
44
+ mode: demo-1
45
+
46
+ EOF
47
+ ```
48
+
49
+ ```text title="stdout"
50
+ configmap/my-config-1 created
51
+ ```
52
+
53
+ **✅ Apply ConfigMap "my-config-2" in namespace "kest-9hdhj"**
54
+
55
+ ```shell
56
+ kubectl apply -f - -n kest-9hdhj <<EOF
57
+ apiVersion: v1
58
+ kind: ConfigMap
59
+ metadata:
60
+ name: my-config-2
61
+ data:
62
+ mode: demo-2
63
+
64
+ EOF
65
+ ```
66
+
67
+ ```text title="stdout"
68
+ configmap/my-config-2 created
69
+ ```
70
+
71
+ **✅ Apply ConfigMap "my-config-3" in namespace "kest-9hdhj"**
72
+
73
+ ```shell
74
+ kubectl apply -f - -n kest-9hdhj <<EOF
75
+ apiVersion: v1
76
+ kind: ConfigMap
77
+ metadata:
78
+ name: my-config-3
79
+ data:
80
+ mode: demo-3
81
+
82
+ EOF
83
+ ```
84
+
85
+ ```text title="stdout"
86
+ configmap/my-config-3 created
87
+ ```
88
+
89
+ ### Then: the ConfigMap should have the expected data
90
+
91
+ **✅ Assert ConfigMap "my-config-1" in namespace "kest-9hdhj"**
92
+
93
+ ```shell
94
+ kubectl get ConfigMap my-config-1 -o yaml -n kest-9hdhj
95
+ ```
96
+
97
+ ```yaml title="stdout"
98
+ apiVersion: v1
99
+ data:
100
+ mode: demo-1
101
+ kind: ConfigMap
102
+ metadata:
103
+ annotations:
104
+ kubectl.kubernetes.io/last-applied-configuration: |
105
+ {"apiVersion":"v1","data":{"mode":"demo-1"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"my-config-1","namespace":"kest-9hdhj"}}
106
+ creationTimestamp: "2026-02-06T00:27:52Z"
107
+ name: my-config-1
108
+ namespace: kest-9hdhj
109
+ resourceVersion: "487392"
110
+ uid: c55d94fa-7096-4534-84ef-88a4e384da24
111
+ ```
112
+
113
+ ### Cleanup
114
+
115
+ | # | Action | Resource | Status |
116
+ |---|--------|----------|--------|
117
+ | 1 | Delete | ConfigMap/my-config-3 | ✅ |
118
+ | 2 | Delete | ConfigMap/my-config-2 | ✅ |
119
+ | 3 | Delete | ConfigMap/my-config-1 | ✅ |
120
+ | 4 | Delete namespace | kest-9hdhj | ✅ |
121
+
122
+ ```shellsession
123
+ $ kubectl delete ConfigMap/my-config-3 -n kest-9hdhj
124
+ configmap "my-config-3" deleted from kest-9hdhj namespace
125
+
126
+ $ kubectl delete ConfigMap/my-config-2 -n kest-9hdhj
127
+ configmap "my-config-2" deleted from kest-9hdhj namespace
128
+
129
+ $ kubectl delete ConfigMap/my-config-1 -n kest-9hdhj
130
+ configmap "my-config-1" deleted from kest-9hdhj namespace
131
+
132
+ $ kubectl delete Namespace/kest-9hdhj
133
+ namespace "kest-9hdhj" deleted
134
+ ```
135
+
136
+ # Example: asserts a non-existent ConfigMap (expected to fail)
137
+
138
+ ## Scenario Overview
139
+
140
+ | # | Action | Resource | Status |
141
+ |---|--------|----------|--------|
142
+ | 1 | Create namespace | kest-k515q | ✅ |
143
+ | 2 | Assert | ConfigMap/non-existent-config | ❌ |
144
+
145
+ ## Scenario Details
146
+
147
+ ### Given: a new namespace exists
148
+
149
+ **✅ Create Namespace "kest-k515q"**
150
+
151
+ ```shell
152
+ kubectl apply -f - <<EOF
153
+ apiVersion: v1
154
+ kind: Namespace
155
+ metadata:
156
+ name: kest-k515q
157
+
158
+ EOF
159
+ ```
160
+
161
+ ```text title="stdout"
162
+ namespace/kest-k515q created
163
+ ```
164
+
165
+ ### Then: asserting a non-existent ConfigMap should fail
166
+
167
+ **❌ Assert ConfigMap "non-existent-config" in namespace "kest-k515q"** (Failed after 20 attempts)
168
+
169
+ ```shell
170
+ kubectl get ConfigMap non-existent-config -o yaml -n kest-k515q
171
+ ```
172
+
173
+ ```text title="stderr"
174
+ Error from server (NotFound): configmaps "non-existent-config" not found
175
+ ```
176
+
177
+ Error:
178
+
179
+ ```text
180
+ kubectl get failed (exit code 1): Error from server (NotFound): configmaps "non-existent-config" not found
181
+
182
+ Trace:
183
+ at runKubectl (/Users/suin/codes/github.com/appthrust/kest/ts/kubectl/index.ts:316:17)
184
+ at async get (/Users/suin/codes/github.com/appthrust/kest/ts/kubectl/index.ts:223:23)
185
+ at async <anonymous> (/Users/suin/codes/github.com/appthrust/kest/ts/actions/assert.ts:11:34)
186
+ at async retryUntil (/Users/suin/codes/github.com/appthrust/kest/ts/retry.ts:83:27)
187
+ at async <anonymous> (/Users/suin/codes/github.com/appthrust/kest/ts/scenario/index.ts:168:20)
188
+ at async <anonymous> (/Users/suin/codes/github.com/appthrust/kest/example/example.test.ts:69:12)
189
+ at async <anonymous> (/Users/suin/codes/github.com/appthrust/kest/ts/test.ts:65:15)
190
+ at processTicksAndRejections (unknown:7:39)
191
+ ```
192
+
193
+ ### Cleanup
194
+
195
+ | # | Action | Resource | Status |
196
+ |---|--------|----------|--------|
197
+ | 1 | Delete namespace | kest-k515q | ✅ |
198
+
199
+ ```shellsession
200
+ $ kubectl delete Namespace/kest-k515q
201
+ namespace "kest-k515q" deleted
202
+ ```
203
+
204
+ # Example: manages resources across multiple clusters
205
+
206
+ ## Scenario Overview
207
+
208
+ | # | Action | Resource | Status |
209
+ |---|--------|----------|--------|
210
+ | 1 | Apply | ConfigMap/my-config-1 | ✅ |
211
+ | 2 | Apply | ConfigMap/my-config-2 | ✅ |
212
+ | 3 | Assert | ConfigMap/my-config-1 | ✅ |
213
+ | 4 | Assert | ConfigMap/my-config-2 | ✅ |
214
+
215
+ ## Scenario Details
216
+
217
+ ### When: I apply ConfigMaps to each cluster
218
+
219
+ **✅ Apply ConfigMap "my-config-1"**
220
+
221
+ ```shell
222
+ kubectl apply -f - --context kind-kest-test-cluster-1 --kubeconfig .kubeconfig.yaml <<EOF
223
+ apiVersion: v1
224
+ kind: ConfigMap
225
+ metadata:
226
+ name: my-config-1
227
+ data:
228
+ mode: demo-1
229
+
230
+ EOF
231
+ ```
232
+
233
+ ```text title="stdout"
234
+ configmap/my-config-1 created
235
+ ```
236
+
237
+ **✅ Apply ConfigMap "my-config-2"**
238
+
239
+ ```shell
240
+ kubectl apply -f - --context kind-kest-test-cluster-2 <<EOF
241
+ apiVersion: v1
242
+ kind: ConfigMap
243
+ metadata:
244
+ name: my-config-2
245
+ data:
246
+ mode: demo-2
247
+
248
+ EOF
249
+ ```
250
+
251
+ ```text title="stdout"
252
+ configmap/my-config-2 created
253
+ ```
254
+
255
+ ### Then: each cluster should have its ConfigMap
256
+
257
+ **✅ Assert ConfigMap "my-config-1"**
258
+
259
+ ```shell
260
+ kubectl get ConfigMap my-config-1 -o yaml --context kind-kest-test-cluster-1 --kubeconfig .kubeconfig.yaml
261
+ ```
262
+
263
+ ```yaml title="stdout"
264
+ apiVersion: v1
265
+ data:
266
+ mode: demo-1
267
+ kind: ConfigMap
268
+ metadata:
269
+ annotations:
270
+ kubectl.kubernetes.io/last-applied-configuration: |
271
+ {"apiVersion":"v1","data":{"mode":"demo-1"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"my-config-1","namespace":"default"}}
272
+ creationTimestamp: "2026-02-06T00:27:58Z"
273
+ name: my-config-1
274
+ namespace: default
275
+ resourceVersion: "487408"
276
+ uid: 3747a790-9971-4d5a-95ad-a27a81a816f6
277
+ ```
278
+
279
+ **✅ Assert ConfigMap "my-config-2"**
280
+
281
+ ```shell
282
+ kubectl get ConfigMap my-config-2 -o yaml --context kind-kest-test-cluster-2
283
+ ```
284
+
285
+ ```yaml title="stdout"
286
+ apiVersion: v1
287
+ data:
288
+ mode: demo-2
289
+ kind: ConfigMap
290
+ metadata:
291
+ annotations:
292
+ kubectl.kubernetes.io/last-applied-configuration: |
293
+ {"apiVersion":"v1","data":{"mode":"demo-2"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"my-config-2","namespace":"default"}}
294
+ creationTimestamp: "2026-02-06T00:27:58Z"
295
+ name: my-config-2
296
+ namespace: default
297
+ resourceVersion: "485245"
298
+ uid: d79c985c-4c4c-4f89-9ec6-7f4a9d675e42
299
+ ```
300
+
301
+ ### Cleanup
302
+
303
+ | # | Action | Resource | Status |
304
+ |---|--------|----------|--------|
305
+ | 1 | Delete | ConfigMap/my-config-2 | ✅ |
306
+ | 2 | Delete | ConfigMap/my-config-1 | ✅ |
307
+
308
+ ```shellsession
309
+ $ kubectl delete ConfigMap/my-config-2 --context kind-kest-test-cluster-2
310
+ configmap "my-config-2" deleted from default namespace
311
+
312
+ $ kubectl delete ConfigMap/my-config-1 --context kind-kest-test-cluster-1 --kubeconfig .kubeconfig.yaml
313
+ configmap "my-config-1" deleted from default namespace
314
+ ```
315
+
316
+ # Example: executes shell commands with revert cleanup
317
+
318
+ ## Scenario Overview
319
+
320
+ | # | Action | Resource | Status |
321
+ |---|--------|----------|--------|
322
+ | 1 | Exec | N/A | ✅ |
323
+
324
+ ## Scenario Details
325
+
326
+ ### Cleanup
327
+
328
+ | # | Action | Resource | Status |
329
+ |---|--------|----------|--------|
330
+ | 1 | Exec | N/A | ✅ |
331
+
332
+ # Example: asserts resource presence and absence in a list
333
+
334
+ ## Scenario Overview
335
+
336
+ | # | Action | Resource | Status |
337
+ |---|--------|----------|--------|
338
+ | 1 | Create namespace | kest-cld1c | ✅ |
339
+ | 2 | Apply | ConfigMap/my-config-1 | ✅ |
340
+ | 3 | AssertList | ConfigMap | ✅ |
341
+
342
+ ## Scenario Details
343
+
344
+ ### Given: a new namespace exists
345
+
346
+ **✅ Create Namespace "kest-cld1c"**
347
+
348
+ ```shell
349
+ kubectl apply -f - <<EOF
350
+ apiVersion: v1
351
+ kind: Namespace
352
+ metadata:
353
+ name: kest-cld1c
354
+
355
+ EOF
356
+ ```
357
+
358
+ ```text title="stdout"
359
+ namespace/kest-cld1c created
360
+ ```
361
+
362
+ ### When: I apply a single ConfigMap
363
+
364
+ **✅ Apply ConfigMap "my-config-1" in namespace "kest-cld1c"**
365
+
366
+ ```shell
367
+ kubectl apply -f - -n kest-cld1c <<EOF
368
+ apiVersion: v1
369
+ kind: ConfigMap
370
+ metadata:
371
+ name: my-config-1
372
+ data:
373
+ mode: demo-1
374
+
375
+ EOF
376
+ ```
377
+
378
+ ```text title="stdout"
379
+ configmap/my-config-1 created
380
+ ```
381
+
382
+ ### Then: the list should contain only the applied ConfigMap
383
+
384
+ **✅ AssertList ConfigMap in namespace "kest-cld1c"**
385
+
386
+ ```shell
387
+ kubectl get ConfigMap -o yaml -n kest-cld1c
388
+ ```
389
+
390
+ ```yaml title="stdout"
391
+ apiVersion: v1
392
+ items:
393
+ - apiVersion: v1
394
+ data:
395
+ ca.crt: |
396
+ -----BEGIN CERTIFICATE-----
397
+ MIIDBTCCAe2gAwIBAgIIVDHmGXfVRt4wDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
398
+ AxMKa3ViZXJuZXRlczAeFw0yNjAxMzEwMTEwMzBaFw0zNjAxMjkwMTE1MzBaMBUx
399
+ EzARBgNVBAMTCmt1YmVybmV0ZXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
400
+ AoIBAQC6b/cTN1kRXpFlzHMTO+KZQGkG0T49Jp/MkbSSDyieRJi3AK8KG1v2xMD5
401
+ CVMDba2oH+Cn4JdZ5ixhOJ4PRP4DUjVYvHWp6Em9n1VtB4QyX9QzJBBsu+0y+vNh
402
+ qGW2TbAcH7BfZi+Gxjrb98QbWbhg1d0drDqyTzA/yrbhRqEX1GwGb//VF06CCp5n
403
+ qktPSZsS265elmQoip5leaM+5hX3CbZvLVWpx5b964VJIuxNodgYVKNfg5K6Dogm
404
+ gjcSNrrJ6e0cszwuYVC0OQFnAThOXwXLyeLnSmgHuRb0tzP/SmWWLXm2SX05uPb9
405
+ vHqVoopdNURi82g1vplP/rb45TA9AgMBAAGjWTBXMA4GA1UdDwEB/wQEAwICpDAP
406
+ BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRK0TknEEFFUSqlS2q4/K48/+GoSTAV
407
+ BgNVHREEDjAMggprdWJlcm5ldGVzMA0GCSqGSIb3DQEBCwUAA4IBAQAguduQSX4i
408
+ ONrSF15diokMuY1+3NY+xufuIFZ8rT5mhZF5cJjt9Av0s9fqDr7urI48a4BCO4Bv
409
+ mdB0hwsn/rKNYx7FgQyyAF1MXEywXjR66tNwCCAibRNs0k6rrWZ0hMvlNB0PkDpE
410
+ VtLYqjN9hzOzBvYKkNoTgAbt510iTPGyQaLlloJYWonsUZOHaYmLrksPAKa6l+WT
411
+ SyNvFcOTgYUXcwTHXDdXN8/nWJy9v9lWGPzyFbA5C0jUvfoWDIejRgxnOS7Tm6Qe
412
+ NU5rKBlVqheAqx+T6toLdlkgOs1wRJQlqu0XO4FcfoYDCUTMW41DeO45i54Ql8rQ
413
+ lAJH8vYIQyps
414
+ -----END CERTIFICATE-----
415
+ kind: ConfigMap
416
+ metadata:
417
+ annotations:
418
+ kubernetes.io/description: Contains a CA bundle that can be used to verify the
419
+ kube-apiserver when using internal endpoints such as the internal service
420
+ IP or kubernetes.default.svc. No other usage is guaranteed across distributions
421
+ of Kubernetes clusters.
422
+ creationTimestamp: "2026-02-06T00:27:58Z"
423
+ name: kube-root-ca.crt
424
+ namespace: kest-cld1c
425
+ resourceVersion: "487416"
426
+ uid: b60235f4-685c-4845-a143-bf89897c801c
427
+ - apiVersion: v1
428
+ data:
429
+ mode: demo-1
430
+ kind: ConfigMap
431
+ metadata:
432
+ annotations:
433
+ kubectl.kubernetes.io/last-applied-configuration: |
434
+ {"apiVersion":"v1","data":{"mode":"demo-1"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"my-config-1","namespace":"kest-cld1c"}}
435
+ creationTimestamp: "2026-02-06T00:27:58Z"
436
+ name: my-config-1
437
+ namespace: kest-cld1c
438
+ resourceVersion: "487417"
439
+ uid: 45e6c2a5-e745-408f-a4dd-b8ca671411c6
440
+ kind: List
441
+ metadata:
442
+ resourceVersion: ""
443
+ ```
444
+
445
+ ### Cleanup
446
+
447
+ | # | Action | Resource | Status |
448
+ |---|--------|----------|--------|
449
+ | 1 | Delete | ConfigMap/my-config-1 | ✅ |
450
+ | 2 | Delete namespace | kest-cld1c | ✅ |
451
+
452
+ ```shellsession
453
+ $ kubectl delete ConfigMap/my-config-1 -n kest-cld1c
454
+ configmap "my-config-1" deleted from kest-cld1c namespace
455
+
456
+ $ kubectl delete Namespace/kest-cld1c
457
+ namespace "kest-cld1c" deleted
458
+ ```
459
+
460
+ # Example: applies status subresource to custom resource
461
+
462
+ ## Scenario Overview
463
+
464
+ | # | Action | Resource | Status |
465
+ |---|--------|----------|--------|
466
+ | 1 | Apply | CustomResourceDefinition/helloworlds.example.com | ✅ |
467
+ | 2 | Create namespace | kest-1j79x | ✅ |
468
+ | 3 | Apply | HelloWorld/my-hello-world | ✅ |
469
+ | 4 | ApplyStatus | HelloWorld/my-hello-world | ✅ |
470
+ | 5 | Assert | HelloWorld/my-hello-world | ✅ |
471
+
472
+ ## Scenario Details
473
+
474
+ ### Given: a HelloWorld custom resource definition exists
475
+
476
+ **✅ Apply CustomResourceDefinition "helloworlds.example.com"**
477
+
478
+ ```shell
479
+ kubectl apply -f - <<EOF
480
+ apiVersion: apiextensions.k8s.io/v1
481
+ kind: CustomResourceDefinition
482
+ metadata:
483
+ name: helloworlds.example.com
484
+ spec:
485
+ group: example.com
486
+ names:
487
+ kind: HelloWorld
488
+ listKind: HelloWorldList
489
+ plural: helloworlds
490
+ singular: helloworld
491
+ scope: Namespaced
492
+ versions:
493
+ - name: v1
494
+ served: true
495
+ storage: true
496
+ subresources:
497
+ status:
498
+ {}
499
+ schema:
500
+ openAPIV3Schema:
501
+ type: object
502
+ properties:
503
+ apiVersion:
504
+ type: string
505
+ kind:
506
+ type: string
507
+ metadata:
508
+ type: object
509
+ status:
510
+ description: HelloWorldStatus defines the observed state of HelloWorld.
511
+ type: object
512
+ properties:
513
+ conditions:
514
+ description: "Conditions represent the latest available observations of an object's state."
515
+ type: array
516
+ x-kubernetes-list-type: map
517
+ x-kubernetes-list-map-keys:
518
+ - type
519
+ items:
520
+ description: Condition contains details for one aspect of the current state of this API Resource.
521
+ type: object
522
+ required:
523
+ - type
524
+ - status
525
+ - lastTransitionTime
526
+ - reason
527
+ - message
528
+ properties:
529
+ type:
530
+ description: Type of condition in CamelCase or in foo.example.com/CamelCase.
531
+ type: string
532
+ maxLength: 316
533
+ pattern: "^[A-Za-z0-9]([A-Za-z0-9_.-]*[A-Za-z0-9])?$"
534
+ status:
535
+ description: "Status of the condition, one of True, False, Unknown."
536
+ type: string
537
+ enum:
538
+ - "True"
539
+ - "False"
540
+ - Unknown
541
+ observedGeneration:
542
+ description: observedGeneration represents the .metadata.generation that the condition was set based upon.
543
+ type: integer
544
+ format: int64
545
+ minimum: 0
546
+ lastTransitionTime:
547
+ description: lastTransitionTime is the last time the condition transitioned from one status to another.
548
+ type: string
549
+ format: date-time
550
+ reason:
551
+ description: "reason contains a programmatic identifier indicating the reason for the condition's last transition."
552
+ type: string
553
+ minLength: 1
554
+ maxLength: 1024
555
+ pattern: "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$"
556
+ message:
557
+ description: message is a human readable message indicating details about the transition.
558
+ type: string
559
+ maxLength: 32768
560
+ - name: v2
561
+ served: true
562
+ storage: false
563
+ subresources:
564
+ status:
565
+ {}
566
+ schema:
567
+ openAPIV3Schema:
568
+ type: object
569
+ properties:
570
+ apiVersion:
571
+ type: string
572
+ kind:
573
+ type: string
574
+ metadata:
575
+ type: object
576
+ status:
577
+ description: HelloWorldStatus defines the observed state of HelloWorld.
578
+ type: object
579
+ properties:
580
+ conditions:
581
+ description: "Conditions represent the latest available observations of an object's state."
582
+ type: array
583
+ x-kubernetes-list-type: map
584
+ x-kubernetes-list-map-keys:
585
+ - type
586
+ items:
587
+ description: Condition contains details for one aspect of the current state of this API Resource.
588
+ type: object
589
+ required:
590
+ - type
591
+ - status
592
+ - lastTransitionTime
593
+ - reason
594
+ - message
595
+ properties:
596
+ type:
597
+ description: Type of condition in CamelCase or in foo.example.com/CamelCase.
598
+ type: string
599
+ maxLength: 316
600
+ pattern: "^[A-Za-z0-9]([A-Za-z0-9_.-]*[A-Za-z0-9])?$"
601
+ status:
602
+ description: "Status of the condition, one of True, False, Unknown."
603
+ type: string
604
+ enum:
605
+ - "True"
606
+ - "False"
607
+ - Unknown
608
+ observedGeneration:
609
+ description: observedGeneration represents the .metadata.generation that the condition was set based upon.
610
+ type: integer
611
+ format: int64
612
+ minimum: 0
613
+ lastTransitionTime:
614
+ description: lastTransitionTime is the last time the condition transitioned from one status to another.
615
+ type: string
616
+ format: date-time
617
+ reason:
618
+ description: "reason contains a programmatic identifier indicating the reason for the condition's last transition."
619
+ type: string
620
+ minLength: 1
621
+ maxLength: 1024
622
+ pattern: "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$"
623
+ message:
624
+ description: message is a human readable message indicating details about the transition.
625
+ type: string
626
+ maxLength: 32768
627
+
628
+ EOF
629
+ ```
630
+
631
+ ```text title="stdout"
632
+ customresourcedefinition.apiextensions.k8s.io/helloworlds.example.com created
633
+ ```
634
+
635
+ ### Given: a new namespace exists
636
+
637
+ **✅ Create Namespace "kest-1j79x"**
638
+
639
+ ```shell
640
+ kubectl apply -f - <<EOF
641
+ apiVersion: v1
642
+ kind: Namespace
643
+ metadata:
644
+ name: kest-1j79x
645
+
646
+ EOF
647
+ ```
648
+
649
+ ```text title="stdout"
650
+ namespace/kest-1j79x created
651
+ ```
652
+
653
+ ### Given: a HelloWorld custom resource is created
654
+
655
+ **✅ Apply HelloWorld "my-hello-world" in namespace "kest-1j79x"**
656
+
657
+ ```shell
658
+ kubectl apply -f - -n kest-1j79x <<EOF
659
+ apiVersion: example.com/v2
660
+ kind: HelloWorld
661
+ metadata:
662
+ name: my-hello-world
663
+
664
+ EOF
665
+ ```
666
+
667
+ ```text title="stdout"
668
+ helloworld.example.com/my-hello-world created
669
+ ```
670
+
671
+ ### When: I apply a status with Ready condition
672
+
673
+ **✅ ApplyStatus HelloWorld "my-hello-world" in namespace "kest-1j79x"**
674
+
675
+ ```shell
676
+ kubectl apply --server-side --field-manager kest --subresource=status -f - -n kest-1j79x <<EOF
677
+ apiVersion: example.com/v2
678
+ kind: HelloWorld
679
+ metadata:
680
+ name: my-hello-world
681
+ status:
682
+ conditions:
683
+ - type: Ready
684
+ status: "True"
685
+ lastTransitionTime: 2026-02-05T00:00:00Z
686
+ reason: ManuallySet
687
+ message: Ready condition set to True via server-side apply.
688
+
689
+ EOF
690
+ ```
691
+
692
+ ```text title="stdout"
693
+ helloworld.example.com/my-hello-world serverside-applied
694
+ ```
695
+
696
+ ### Then: the HelloWorld should have the Ready status
697
+
698
+ **✅ Assert HelloWorld "my-hello-world" in namespace "kest-1j79x"**
699
+
700
+ ```shell
701
+ kubectl get HelloWorld.v2.example.com my-hello-world -o yaml -n kest-1j79x
702
+ ```
703
+
704
+ ```yaml title="stdout"
705
+ apiVersion: example.com/v2
706
+ kind: HelloWorld
707
+ metadata:
708
+ annotations:
709
+ kubectl.kubernetes.io/last-applied-configuration: |
710
+ {"apiVersion":"example.com/v2","kind":"HelloWorld","metadata":{"annotations":{},"name":"my-hello-world","namespace":"kest-1j79x"}}
711
+ creationTimestamp: "2026-02-06T00:28:06Z"
712
+ generation: 1
713
+ name: my-hello-world
714
+ namespace: kest-1j79x
715
+ resourceVersion: "487441"
716
+ uid: 681105e4-0004-4bf4-a6b2-68fe5320e5d5
717
+ status:
718
+ conditions:
719
+ - lastTransitionTime: "2026-02-05T00:00:00Z"
720
+ message: Ready condition set to True via server-side apply.
721
+ reason: ManuallySet
722
+ status: "True"
723
+ type: Ready
724
+ ```
725
+
726
+ ### Cleanup
727
+
728
+ | # | Action | Resource | Status |
729
+ |---|--------|----------|--------|
730
+ | 1 | Delete | HelloWorld/my-hello-world | ✅ |
731
+ | 2 | Delete namespace | kest-1j79x | ✅ |
732
+ | 3 | Delete | CustomResourceDefinition/helloworlds.example.com | ✅ |
733
+
734
+ ```shellsession
735
+ $ kubectl delete HelloWorld/my-hello-world -n kest-1j79x
736
+ helloworld.example.com "my-hello-world" deleted from kest-1j79x namespace
737
+
738
+ $ kubectl delete Namespace/kest-1j79x
739
+ namespace "kest-1j79x" deleted
740
+
741
+ $ kubectl delete CustomResourceDefinition/helloworlds.example.com
742
+ customresourcedefinition.apiextensions.k8s.io "helloworlds.example.com" deleted
743
+ ```
744
+