Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,5 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

/swagger.json
125 changes: 125 additions & 0 deletions fetch_swagger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/usr/bin/env python3
"""Fetch and mutate Kubernetes Python client swagger specs.

This script downloads the upstream swagger.json file for a supplied git ref,
applies the custom mutations that are checked into this repository, and writes
the result to stdout.
"""

from __future__ import annotations

import argparse
import json
import sys
from typing import Any
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen


RAW_BASE_URL = (
"https://raw.githubusercontent.com/kubernetes-client/python/refs/heads/{ref}/scripts/swagger.json"
)

INT_OR_STRING_DEFINITION = {
"description": "It's an int or a string.",
"oneOf": [
{"type": "integer"},
{"type": "string"},
],
}

HTTP_GET_ACTION_PORT_PATH: tuple[str | int, ...] = (
"definitions",
"v1.HTTPGetAction",
"properties",
"port",
)


def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"ref",
help="Git reference to fetch from (e.g. release-29.0).",
)
return parser.parse_args()


def fetch_swagger(ref: str) -> dict[str, Any]:
url = RAW_BASE_URL.format(ref=ref)
request = Request(url, headers={"User-Agent": "k8-swagger-fetcher"})
try:
with urlopen(request) as response:
content = response.read()
except HTTPError as exc:
raise SystemExit(f"Failed to fetch {url} (HTTP {exc.code}): {exc.reason}") from exc
except URLError as exc:
raise SystemExit(f"Failed to fetch {url}: {exc.reason}") from exc

try:
return json.loads(content.decode("utf-8"))
except json.JSONDecodeError as exc:
raise SystemExit(f"Unable to parse swagger.json from {url}: {exc}") from exc


def ensure_int_or_string_definition(spec: dict[str, Any]) -> None:
definitions = spec.setdefault("definitions", {})

existing = definitions.get("v1.IntOrString")
if existing == INT_OR_STRING_DEFINITION:
return

if "v1.IntOrString" in definitions:
definitions["v1.IntOrString"] = INT_OR_STRING_DEFINITION
return

new_definitions = {"v1.IntOrString": INT_OR_STRING_DEFINITION}
new_definitions.update(definitions)
spec["definitions"] = new_definitions


def mutate_int_or_string_nodes(spec: dict[str, Any]) -> None:
def walk(node: Any, path: tuple[str | int, ...]) -> None:
if isinstance(node, dict):
if node.get("format") == "int-or-string":
if path == HTTP_GET_ACTION_PORT_PATH:
description = node.get("description")
node.clear()
node["$ref"] = "#/definitions/v1.IntOrString"
if description is not None:
node["description"] = description
else:
if node.get("type") != "string":
node["type"] = "string"
for key, value in list(node.items()):
walk(value, (*path, key))
elif isinstance(node, list):
for index, item in enumerate(node):
walk(item, (*path, index))

walk(spec, ())


def mutate_spec(spec: dict[str, Any]) -> dict[str, Any]:
ensure_int_or_string_definition(spec)
mutate_int_or_string_nodes(spec)
return spec


def encode_spec(spec: dict[str, Any]) -> str:
return json.dumps(spec, indent=2)


def main() -> None:
args = parse_args()
spec = fetch_swagger(args.ref)
mutated = mutate_spec(spec)
encoded = encode_spec(mutated)
try:
sys.stdout.write(encoded)
except BrokenPipeError:
pass


if __name__ == "__main__":
main()
13 changes: 13 additions & 0 deletions refresh.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
#!/bin/bash

set -euo pipefail

SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )

if [ "$#" -ne 1 ]; then
echo "Usage: $0 <git-ref>" >&2
exit 1
fi

REF="$1"

echo "Fetching swagger.json for ${REF}..."
python3 "$SCRIPT_DIR/fetch_swagger.py" "$REF" > "$SCRIPT_DIR/swagger.json"

GEN_DIR=$(mktemp -d 2>/dev/null || mktemp -d -t 'k8')

echo "Generating full client from OpenAPI spec..."
Expand Down
2 changes: 1 addition & 1 deletion src/k8/admissionregistration_v1_service_reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)

The version of the OpenAPI document: release-1.29
The version of the OpenAPI document: release-1.34
Generated by OpenAPI Generator (https://openapi-generator.tech)

Do not edit the class manually.
Expand Down
2 changes: 1 addition & 1 deletion src/k8/admissionregistration_v1_webhook_client_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)

The version of the OpenAPI document: release-1.29
The version of the OpenAPI document: release-1.34
Generated by OpenAPI Generator (https://openapi-generator.tech)

Do not edit the class manually.
Expand Down
2 changes: 1 addition & 1 deletion src/k8/apiextensions_v1_service_reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)

The version of the OpenAPI document: release-1.29
The version of the OpenAPI document: release-1.34
Generated by OpenAPI Generator (https://openapi-generator.tech)

Do not edit the class manually.
Expand Down
2 changes: 1 addition & 1 deletion src/k8/apiextensions_v1_webhook_client_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)

The version of the OpenAPI document: release-1.29
The version of the OpenAPI document: release-1.34
Generated by OpenAPI Generator (https://openapi-generator.tech)

Do not edit the class manually.
Expand Down
2 changes: 1 addition & 1 deletion src/k8/apiregistration_v1_service_reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)

The version of the OpenAPI document: release-1.29
The version of the OpenAPI document: release-1.34
Generated by OpenAPI Generator (https://openapi-generator.tech)

Do not edit the class manually.
Expand Down
2 changes: 1 addition & 1 deletion src/k8/authentication_v1_token_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)

The version of the OpenAPI document: release-1.29
The version of the OpenAPI document: release-1.34
Generated by OpenAPI Generator (https://openapi-generator.tech)

Do not edit the class manually.
Expand Down
4 changes: 2 additions & 2 deletions src/k8/core_v1_endpoint_port.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)

The version of the OpenAPI document: release-1.29
The version of the OpenAPI document: release-1.34
Generated by OpenAPI Generator (https://openapi-generator.tech)

Do not edit the class manually.
Expand All @@ -24,7 +24,7 @@

class CoreV1EndpointPort(BaseModel):
"""
EndpointPort is a tuple that describes a single port.
EndpointPort is a tuple that describes a single port. Deprecated: This API is deprecated in v1.33+.
""" # noqa: E501
app_protocol: Optional[StrictStr] = Field(default=None, description="The application protocol for this port. This is used as a hint for implementations to offer richer behavior for protocols that they understand. This field follows standard Kubernetes label syntax. Valid values are either: * Un-prefixed protocol names - reserved for IANA standard service names (as per RFC-6335 and https://www.iana.org/assignments/service-names). * Kubernetes-defined prefixed names: * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 * Other protocols should use implementation-defined prefixed names such as mycompany.com/my-custom-protocol.", alias="appProtocol")
name: Optional[StrictStr] = Field(default=None, description="The name of this port. This must match the 'name' field in the corresponding ServicePort. Must be a DNS_LABEL. Optional only if one port is defined.")
Expand Down
2 changes: 1 addition & 1 deletion src/k8/core_v1_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)

The version of the OpenAPI document: release-1.29
The version of the OpenAPI document: release-1.34
Generated by OpenAPI Generator (https://openapi-generator.tech)

Do not edit the class manually.
Expand Down
2 changes: 1 addition & 1 deletion src/k8/core_v1_event_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)

The version of the OpenAPI document: release-1.29
The version of the OpenAPI document: release-1.34
Generated by OpenAPI Generator (https://openapi-generator.tech)

Do not edit the class manually.
Expand Down
2 changes: 1 addition & 1 deletion src/k8/core_v1_event_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)

The version of the OpenAPI document: release-1.29
The version of the OpenAPI document: release-1.34
Generated by OpenAPI Generator (https://openapi-generator.tech)

Do not edit the class manually.
Expand Down
102 changes: 102 additions & 0 deletions src/k8/core_v1_resource_claim.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# coding: utf-8

"""
Kubernetes

No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)

The version of the OpenAPI document: release-1.34
Generated by OpenAPI Generator (https://openapi-generator.tech)

Do not edit the class manually.
""" # noqa: E501


from __future__ import annotations
import pprint
import re # noqa: F401
import json

from pydantic import BaseModel, ConfigDict, Field, StrictStr
from typing import Any, ClassVar, Dict, List, Optional
from typing import Optional, Set
from typing_extensions import Self

class CoreV1ResourceClaim(BaseModel):
"""
ResourceClaim references one entry in PodSpec.ResourceClaims.
""" # noqa: E501
name: StrictStr = Field(description="Name must match the name of one entry in pod.spec.resourceClaims of the Pod where this field is used. It makes that resource available inside a container.")
request: Optional[StrictStr] = Field(default=None, description="Request is the name chosen for a request in the referenced claim. If empty, everything from the claim is made available, otherwise only the result of this request.")
additional_properties: Dict[str, Any] = {}
__properties: ClassVar[List[str]] = ["name", "request"]

model_config = ConfigDict(
populate_by_name=True,
validate_assignment=True,
protected_namespaces=(),
)


def to_str(self) -> str:
"""Returns the string representation of the model using alias"""
return pprint.pformat(self.model_dump(by_alias=True))

def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
# TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
return json.dumps(self.to_dict())

@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
"""Create an instance of CoreV1ResourceClaim from a JSON string"""
return cls.from_dict(json.loads(json_str))

def to_dict(self) -> Dict[str, Any]:
"""Return the dictionary representation of the model using alias.

This has the following differences from calling pydantic's
`self.model_dump(by_alias=True)`:

* `None` is only added to the output dict for nullable fields that
were set at model initialization. Other fields with value `None`
are ignored.
* Fields in `self.additional_properties` are added to the output dict.
"""
excluded_fields: Set[str] = set([
"additional_properties",
])

_dict = self.model_dump(
by_alias=True,
exclude=excluded_fields,
exclude_none=True,
)
# puts key-value pairs in additional_properties in the top level
if self.additional_properties is not None:
for _key, _value in self.additional_properties.items():
_dict[_key] = _value

return _dict

@classmethod
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"""Create an instance of CoreV1ResourceClaim from a dict"""
if obj is None:
return None

if not isinstance(obj, dict):
return cls.model_validate(obj)

_obj = cls.model_validate({
"name": obj.get("name"),
"request": obj.get("request")
})
# store additional fields in additional_properties
for _key in obj.keys():
if _key not in cls.__properties:
_obj.additional_properties[_key] = obj.get(_key)

return _obj


4 changes: 2 additions & 2 deletions src/k8/discovery_v1_endpoint_port.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)

The version of the OpenAPI document: release-1.29
The version of the OpenAPI document: release-1.34
Generated by OpenAPI Generator (https://openapi-generator.tech)

Do not edit the class manually.
Expand All @@ -28,7 +28,7 @@ class DiscoveryV1EndpointPort(BaseModel):
""" # noqa: E501
app_protocol: Optional[StrictStr] = Field(default=None, description="The application protocol for this port. This is used as a hint for implementations to offer richer behavior for protocols that they understand. This field follows standard Kubernetes label syntax. Valid values are either: * Un-prefixed protocol names - reserved for IANA standard service names (as per RFC-6335 and https://www.iana.org/assignments/service-names). * Kubernetes-defined prefixed names: * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior- * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455 * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455 * Other protocols should use implementation-defined prefixed names such as mycompany.com/my-custom-protocol.", alias="appProtocol")
name: Optional[StrictStr] = Field(default=None, description="name represents the name of this port. All ports in an EndpointSlice must have a unique name. If the EndpointSlice is derived from a Kubernetes service, this corresponds to the Service.ports[].name. Name must either be an empty string or pass DNS_LABEL validation: * must be no more than 63 characters long. * must consist of lower case alphanumeric characters or '-'. * must start and end with an alphanumeric character. Default is empty string.")
port: Optional[StrictInt] = Field(default=None, description="port represents the port number of the endpoint. If this is not specified, ports are not restricted and must be interpreted in the context of the specific consumer.")
port: Optional[StrictInt] = Field(default=None, description="port represents the port number of the endpoint. If the EndpointSlice is derived from a Kubernetes service, this must be set to the service's target port. EndpointSlices used for other purposes may have a nil port.")
protocol: Optional[StrictStr] = Field(default=None, description="protocol represents the IP protocol for this port. Must be UDP, TCP, or SCTP. Default is TCP.")
additional_properties: Dict[str, Any] = {}
__properties: ClassVar[List[str]] = ["appProtocol", "name", "port", "protocol"]
Expand Down
2 changes: 1 addition & 1 deletion src/k8/events_v1_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)

The version of the OpenAPI document: release-1.29
The version of the OpenAPI document: release-1.34
Generated by OpenAPI Generator (https://openapi-generator.tech)

Do not edit the class manually.
Expand Down
2 changes: 1 addition & 1 deletion src/k8/events_v1_event_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)

The version of the OpenAPI document: release-1.29
The version of the OpenAPI document: release-1.34
Generated by OpenAPI Generator (https://openapi-generator.tech)

Do not edit the class manually.
Expand Down
2 changes: 1 addition & 1 deletion src/k8/events_v1_event_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)

The version of the OpenAPI document: release-1.29
The version of the OpenAPI document: release-1.34
Generated by OpenAPI Generator (https://openapi-generator.tech)

Do not edit the class manually.
Expand Down
Loading