Resources discovery¶
kmock.KubernetesScaffold (so as its descendant kmock.KubernetesEmulator) expose the typical Kubernetes endpoints for the cluster information and resource discovery:
//version/api/api/v1/apis/apis/{group}/apis/{group}/{version}
The scaffold & emulator also render the errors in the Kubernetes JSON dict of kind: Status — the same as Kubernetes itself does.
To discover the information, the API uses the following sources:
The
kmock.resourcesassociative array — as is, not processed or filtered.For the emulator, the
kmock.objectsassociative array — also not processed or filtered.All the regular request criteria with the precisely defined resources, i.e. those with all three identifying fields present: group, version, plural.
Resource identifiers¶
All three methods allow specifying the resources either as instances of the kmock.resource; or instances of any class that has the three identifying fields —group, version, plural— such as kopf.Resource; or as a string in one of the recognized format with these three fields:
v1/podspods.v1kopf.dev/v1/kopfexampleskopfexamples.v1.kopf.dev
For the so called “Core API” (the legacy of Kubernetes before the groups were introduced), the group name is an empty string (""), and the version is always "v1" — specifically this combination is recognized by the resource parser.
Examples addressing the kmock.resources associative array in all supported ways:
import kmock
async def test_resource_addressing(kmock: kmock.KubernetesScaffold) -> None:
# Explicit kwargs to the resource class:
kmock.resources[kmock.resource(group='', version='v1', plural='pods')] = {}
kmock.resources[kmock.resource(group='kopf.dev', version='v1', plural='kopfexamples')] = {}
# Positional args to the resource class:
kmock.resources[kmock.resource('', 'v1', 'pods')] = {}
kmock.resources[kmock.resource('kopf.dev', 'v1', 'kopfexamples')] = {}
# Parseable strings to the resource class:
kmock.resources[kmock.resource('v1/pods')] = {}
kmock.resources[kmock.resource('pods.v1')] = {}
kmock.resources[kmock.resource('kopf.dev/v1/kopfexamples')] = {}
kmock.resources[kmock.resource('kopfexamples.v1.kopf.dev')] = {}
# Parseable strings directly as keys (recommended):
kmock.resources['v1/pods'] = {}
kmock.resources['pods.v1'] = {}
kmock.resources['kopf.dev/v1/kopfexamples'] = {}
kmock.resources['kopfexamples.v1.kopf.dev'] = {}
For the presence of the resource, the regular payloads are used, so the resource can be specified without the meta-information this way — and still be visible to the cluster & resource discovery:
import kmock
async def test_resource_adding_via_criteria(kmock: kmock.KubernetesScaffold) -> None:
kmock['list kopf.dev/v1/kopfexamples'] << {'items': []}
Resource meta-information¶
Only the kmock.resources associative array allows adding the extended meta-information about the resources beyond the three identifying fields (group, version, plural) — via the kmock.ResourceInfo or plain dicts with the same keys. These extra fields/keys include:
kind(string, usually capitalized)singularname (string, usally lower-cased)categories(a set of strings)subresources(a set of strings)shortnames(a set of strings; aka aliases)verbs(a set of strings)namespacedflag (boolean; if False, the the cluster-wide resource; if None, then undefined)
The resource meta-information can be added as a single object:
import kmock
async def test_resource_information_as_one_object(kmock: kmock.KubernetesScaffold) -> None:
kmock.resources['v1/pods'] = kmock.ResourceInfo(
kind='Pod',
singular='pod',
shortnames={'po'},
categories={'category1', 'category2'},
verbs={'get', 'post', 'patch', 'delete'},
subresources={'status'},
namespaced=True,
)
The resource meta-information can be added as a dictm, in which case it is implicitly converted to a new instance of kmock.ResourceInfo:
import kmock
async def test_resource_information_as_one_dict(kmock: kmock.KubernetesScaffold) -> None:
kmock.resources['v1/pods'] = {
'kind': 'Pod',
'singular': 'pod',
'shortnames': {'po'},
'categories': {'category1', 'category2'},
'verbs': {'get', 'post', 'patch', 'delete'},
'subresources': {'status'},
'namespaced': True,
}
For brevity, the resource meta-information can be added on a field-by-field basis — in that case, the empty instance of kmock.ResourceInfo is created if it is absent:
import kmock
async def test_resource_information_field_by_field(kmock: kmock.KubernetesScaffold) -> None:
kmock.resources['v1/pods'].kind = 'Pod'
kmock.resources['v1/pods'].singular = 'pod'
kmock.resources['v1/pods'].shortnames = {'po'}
kmock.resources['v1/pods'].categories = {'category1', 'category2'}
kmock.resources['v1/pods'].verbs = {'get', 'post', 'patch', 'delete'}
kmock.resources['v1/pods'].subresources = {'status'}
kmock.resources['v1/pods'].namespaced = True
The meta-information is not used anywhere at the runtime of the scaffold or of the emulator, except for the resource discovery endpoints and their responses — which can be used by other Kubernetes clients, such as kubectl:
import kmock
async def test_resource_information_discovery(kmock: kmock.KubernetesScaffold) -> None:
kmock.resources['kopf.dev/v1/kopfexamples'].kind = 'KopfExample'
kmock.resources['kopf.dev/v1/kopfexamples'].singular = 'kopfexample'
kmock.resources['kopf.dev/v1/kopfexamples'].shortnames = {'kex'}
kmock.resources['kopf.dev/v1/kopfexamples'].categories = {'category1', 'category2'}
kmock.resources['kopf.dev/v1/kopfexamples'].verbs = {'get', 'post', 'patch', 'delete'}
kmock.resources['kopf.dev/v1/kopfexamples'].subresources = {'status'}
kmock.resources['kopf.dev/v1/kopfexamples'].namespaced = True
resp = await kmock.get('/apis/kopf.dev/v1')
data = await resp.read()
assert data == {
'apiVersion': 'v1',
'kind': 'APIResourceList',
'groupVersion': f'kopf.dev/v1',
'resources': [
{
'name': f'kopfexamples',
'kind': 'KopfExample',
'singularName': 'kopfexample',
'shortNames': ['kex'],
'categories': ['category1', 'category2'],
'verbs': ['get', 'post', 'patch', 'delete'],
'namespaced': True,
},
{
'name': f'kopfexamples/status',
'kind': 'KopfExample',
'singularName': 'kopfexample',
'shortNames': ['kex'],
'categories': ['category1', 'category2'],
'verbs': ['get', 'post', 'patch', 'delete'],
'namespaced': True,
},
],
}