diff --git a/class_generator/README.md b/class_generator/README.md index 82d3adc17c..50a228beb9 100644 --- a/class_generator/README.md +++ b/class_generator/README.md @@ -1,5 +1,14 @@ # Resource class generator +Utility to add a python module with class resources to openshift-python-wrapper based on `kind` + +Some resources have the same kind, in this case the script will create two files. +For example `DNS` kind have two CRDs, one from `config.openshift.io` and one from `operator.openshift.io` +The output will be: + +- dns_operator_openshift_io.py +- dns_config_openshift_io.py + ## Usage ### Installation diff --git a/class_generator/class_generator.py b/class_generator/class_generator.py index efec6c0d66..8d844ca716 100644 --- a/class_generator/class_generator.py +++ b/class_generator/class_generator.py @@ -6,9 +6,10 @@ import os import sys from pathlib import Path +from packaging.version import Version import textwrap -from typing import Any, Dict, List, Optional, Set, Tuple +from typing import Any, Dict, List, Tuple import click import re import requests @@ -31,76 +32,65 @@ LOGGER = get_logger(name="class_generator") TESTS_MANIFESTS_DIR: str = "class_generator/tests/manifests" SCHEMA_DIR: str = "class_generator/schema" +SCHEMA_DEFINITION_FILE: str = os.path.join(SCHEMA_DIR, "_definitions.json") RESOURCES_MAPPING_FILE: str = os.path.join(SCHEMA_DIR, "__resources-mappings.json") -def _is_kind_is_namespaced(_kind: str) -> Tuple[bool, str]: - return ( - run_command( - command=shlex.split(f"bash -c 'oc api-resources --namespaced | grep -w {_kind} | wc -l'"), - check=False, - )[1].strip() - == "1" - ), _kind - +def _is_kind_and_namespaced(client: str, _data: Dict[str, Any]) -> Dict[str, Any]: + x_kubernetes_group_version_kind = _data["x-kubernetes-group-version-kind"][0] + _kind = x_kubernetes_group_version_kind["kind"] + _group = x_kubernetes_group_version_kind.get("group") + _version = x_kubernetes_group_version_kind.get("version") + _group_and_version = f"{_group}/{_version}" if _group else _version + + if run_command(command=shlex.split(f"{client} explain {_kind}"), check=False, log_errors=False)[0]: + namespaced = ( + run_command( + command=shlex.split( + f"bash -c '{client} api-resources --namespaced | grep -w {_kind} | grep {_group_and_version} | wc -l'" + ), + check=False, + )[1].strip() + == "1" + ) + _data["namespaced"] = namespaced + return {"is_kind": True, "kind": _kind, "data": _data} -def _is_resource(_kind: str) -> Tuple[bool, str]: - return run_command(command=shlex.split(f"oc explain {_kind}"), check=False, log_errors=False)[0], _kind + return {"is_kind": False, "kind": _kind} -def map_kind_to_namespaced(): +def map_kind_to_namespaced(client: str): not_kind_file: str = os.path.join(SCHEMA_DIR, "__not-kind.txt") if os.path.isfile(not_kind_file): with open(not_kind_file) as fd: not_kind_list = fd.read().split("\n") else: - not_kind_list: List[str] = [] - - with open(os.path.join(f"{SCHEMA_DIR}/all.json")) as fd: - all_json_data = json.load(fd) - - if os.path.isfile(RESOURCES_MAPPING_FILE): - resources_mapping = read_resources_mapping_file() - - else: - resources_mapping: Dict[str, Dict[str, bool]] = {} + not_kind_list = [] - # `all.json` list all files that `openapi2jsonschema` generated which include duplication - kind_set: Set[str] = set() - for _def in all_json_data["oneOf"]: - _kind = _def["$ref"].rsplit(".", 1)[-1] - if _kind in not_kind_list: - continue + with open(SCHEMA_DEFINITION_FILE) as fd: + _definitions_json_data = json.load(fd) - kind_set.add(_kind) + resources_mapping: Dict[Any, List[Any]] = {} - kind_list: List[str] = [] - is_kind_futures: List[Future] = [] + _kind_data_futures: List[Future] = [] with ThreadPoolExecutor() as executor: - for _kind in kind_set: - if resources_mapping.get(_kind.lower()): # resource_mappings store all keys in lowercase + for _data in _definitions_json_data["definitions"].values(): + _group_version_kind = _data.get("x-kubernetes-group-version-kind") + if not _group_version_kind: continue - # Check if the kind we work on is a real kind - is_kind_futures.append(executor.submit(_is_resource, _kind=_kind)) + if _group_version_kind[0]["kind"] in not_kind_list: + continue - for _res in as_completed(is_kind_futures): - res = _res.result() + _kind_data_futures.append(executor.submit(_is_kind_and_namespaced, client=client, _data=_data)) - if res[0]: - kind_list.append(res[1]) + for res in as_completed(_kind_data_futures): + _res = res.result() + if _res["is_kind"]: + resources_mapping.setdefault(_res["kind"].lower(), []).append(_res["data"]) else: - not_kind_list.append(res[1]) - - mapping_kind_futures: List[Future] = [] - with ThreadPoolExecutor() as executor: - for _kind in kind_list: - mapping_kind_futures.append(executor.submit(_is_kind_is_namespaced, _kind=_kind)) - - for _res in as_completed(mapping_kind_futures): - res = _res.result() - resources_mapping[res[1].lower()] = {"namespaced": res[0]} + not_kind_list.append(_res["kind"]) with open(RESOURCES_MAPPING_FILE, "w") as fd: json.dump(resources_mapping, fd) @@ -114,8 +104,41 @@ def read_resources_mapping_file() -> Dict[Any, Any]: return json.load(fd) +def get_server_version(client: str): + rc, out, _ = run_command(command=shlex.split(f"{client} version -o json"), check=False) + if not rc: + LOGGER.error("Failed to get server version") + sys.exit(1) + + json_out = json.loads(out) + return json_out["serverVersion"]["gitVersion"] + + +def get_client_binary() -> str: + if os.system("which oc") == 0: + return "oc" + + elif os.system("which kubectl") == 0: + return "kubectl" + else: + LOGGER.error("Failed to find oc or kubectl") + sys.exit(1) + + +def check_minimum_cluster_version(client) -> None: + cluster_version = get_server_version(client=client) + minimum_server_version = "v1.30.0" + if Version(cluster_version) < Version(minimum_server_version): + LOGGER.error( + f"Server version {cluster_version} is not supported. Minimum supported version is {minimum_server_version}" + ) + sys.exit(1) + + def update_kind_schema(): openapi2jsonschema_str: str = "openapi2jsonschema" + client = get_client_binary() + check_minimum_cluster_version(client=client) if not run_command(command=shlex.split("which openapi2jsonschema"), check=False, log_errors=False)[0]: LOGGER.error( @@ -123,14 +146,16 @@ def update_kind_schema(): ) sys.exit(1) - rc, token, _ = run_command(command=shlex.split("oc whoami -t"), check=False, log_errors=False) + rc, token, _ = run_command(command=shlex.split(f"{client} whoami -t"), check=False, log_errors=False) if not rc: LOGGER.error( - "Failed to get token.\nMake sure you are logged in to the cluster using user and password using `oc login`" + f"Failed to get token.\nMake sure you are logged in to the cluster using user and password using `{client} login`" ) sys.exit(1) - api_url = run_command(command=shlex.split("oc whoami --show-server"), check=False, log_errors=False)[1].strip() + api_url = run_command(command=shlex.split(f"{client} whoami --show-server"), check=False, log_errors=False)[ + 1 + ].strip() data = requests.get(f"{api_url}/openapi/v2", headers={"Authorization": f"Bearer {token.strip()}"}, verify=False) if not data.ok: @@ -145,7 +170,7 @@ def update_kind_schema(): LOGGER.error("Failed to generate schema.") sys.exit(1) - map_kind_to_namespaced() + map_kind_to_namespaced(client=client) def convert_camel_case_to_snake_case(string_: str) -> str: @@ -266,7 +291,13 @@ def generate_resource_file_from_dict( dry_run: bool = False, output_file: str = "", add_tests: bool = False, + output_file_suffix: str = "", + output_dir: str = "", ) -> Tuple[str, str]: + base_dir = output_dir or "ocp_resources" + if not os.path.exists(base_dir): + os.makedirs(base_dir) + rendered = render_jinja_template( template_dict=resource_dict, template_dir="class_generator/manifests", @@ -274,6 +305,7 @@ def generate_resource_file_from_dict( ) formatted_kind_str = convert_camel_case_to_snake_case(string_=resource_dict["kind"]) + _file_suffix = f"{'_' + output_file_suffix if output_file_suffix else ''}" if add_tests: overwrite = True @@ -281,13 +313,13 @@ def generate_resource_file_from_dict( if not os.path.exists(tests_path): os.makedirs(tests_path) - _output_file = os.path.join(tests_path, f"{formatted_kind_str}_res.py") + _output_file = os.path.join(tests_path, f"{formatted_kind_str}{_file_suffix}.py") elif output_file: _output_file = output_file else: - _output_file = os.path.join("ocp_resources", f"{formatted_kind_str}.py") + _output_file = os.path.join(base_dir, f"{formatted_kind_str}{_file_suffix}.py") orig_filename = _output_file if os.path.exists(_output_file): @@ -379,80 +411,77 @@ def prepare_property_dict( def parse_explain( - kind_schema_file: str, - namespaced: Optional[bool] = None, -) -> Dict[str, Any]: - with open(kind_schema_file) as fd: - kind_schema = json.load(fd) - - resource_dict: Dict[str, Any] = { - "base_class": "NamespacedResource" if namespaced else "Resource", - "description": kind_schema["description"], - "fields": [], - "spec": [], - } - - schema_properties: Dict[str, Any] = kind_schema["properties"] - fields_requeired = kind_schema.get("required", []) - resource_dict.update(kind_schema["x-kubernetes-group-version-kind"][0]) + kind: str, +) -> List[Dict[str, Any]]: + _schema_definition = read_resources_mapping_file() + _resources: List[Dict[str, Any]] = [] + + _kinds_schema = _schema_definition[kind.lower()] + for _kind_schema in _kinds_schema: + namespaced = _kind_schema["namespaced"] + resource_dict: Dict[str, Any] = { + "base_class": "NamespacedResource" if namespaced else "Resource", + "description": _kind_schema["description"], + "fields": [], + "spec": [], + } + + schema_properties: Dict[str, Any] = _kind_schema["properties"] + fields_required = _kind_schema.get("required", []) + resource_dict.update(_kind_schema["x-kubernetes-group-version-kind"][0]) + + if spec_schema := schema_properties.get("spec", {}): + spec_schema = get_property_schema(property=spec_schema) + spec_required = spec_schema.get("required", []) + resource_dict = prepare_property_dict( + schema=spec_schema.get("properties", {}), + required=spec_required, + resource_dict=resource_dict, + dict_key="spec", + ) - if spec_schema := schema_properties.get("spec", {}): - spec_schema = get_property_schema(property=spec_schema) - spec_requeired = spec_schema.get("required", []) resource_dict = prepare_property_dict( - schema=spec_schema.get("properties", {}), - required=spec_requeired, + schema=schema_properties, + required=fields_required, resource_dict=resource_dict, - dict_key="spec", + dict_key="fields", ) - resource_dict = prepare_property_dict( - schema=schema_properties, - required=fields_requeired, - resource_dict=resource_dict, - dict_key="fields", - ) - - api_group_real_name = resource_dict.get("group") - # If API Group is not present in resource, try to get it from VERSION - if not api_group_real_name: - version_splited = resource_dict["version"].split("/") - if len(version_splited) == 2: - api_group_real_name = version_splited[0] - - if api_group_real_name: - api_group_for_resource_api_group = api_group_real_name.upper().replace(".", "_") - resource_dict["group"] = api_group_for_resource_api_group - missing_api_group_in_resource: bool = not hasattr(Resource.ApiGroup, api_group_for_resource_api_group) - - if missing_api_group_in_resource: - LOGGER.warning( - f"Missing API Group in Resource\n" - f"Please add `Resource.ApiGroup.{api_group_for_resource_api_group} = {api_group_real_name}` " - "manually into ocp_resources/resource.py under Resource class > ApiGroup class." - ) + api_group_real_name = resource_dict.get("group") + # If API Group is not present in resource, try to get it from VERSION + if not api_group_real_name: + version_splited = resource_dict["version"].split("/") + if len(version_splited) == 2: + api_group_real_name = version_splited[0] + + if api_group_real_name: + api_group_for_resource_api_group = api_group_real_name.upper().replace(".", "_") + resource_dict["group"] = api_group_for_resource_api_group + missing_api_group_in_resource: bool = not hasattr(Resource.ApiGroup, api_group_for_resource_api_group) + + if missing_api_group_in_resource: + LOGGER.warning( + f"Missing API Group in Resource\n" + f"Please add `Resource.ApiGroup.{api_group_for_resource_api_group} = {api_group_real_name}` " + "manually into ocp_resources/resource.py under Resource class > ApiGroup class." + ) - else: - api_version_for_resource_api_version = resource_dict["version"].upper() - missing_api_version_in_resource: bool = not hasattr(Resource.ApiVersion, api_version_for_resource_api_version) - - if missing_api_version_in_resource: - LOGGER.warning( - f"Missing API Version in Resource\n" - f"Please add `Resource.ApiVersion.{api_version_for_resource_api_version} = {resource_dict['version']}` " - "manually into ocp_resources/resource.py under Resource class > ApiGroup class." + else: + api_version_for_resource_api_version = resource_dict["version"].upper() + missing_api_version_in_resource: bool = not hasattr( + Resource.ApiVersion, api_version_for_resource_api_version ) - return resource_dict - + if missing_api_version_in_resource: + LOGGER.warning( + f"Missing API Version in Resource\n" + f"Please add `Resource.ApiVersion.{api_version_for_resource_api_version} = {resource_dict['version']}` " + "manually into ocp_resources/resource.py under Resource class > ApiGroup class." + ) -def get_kind_schema_file(kind: str) -> str: - kind_file = os.path.join(SCHEMA_DIR, f"{kind.lower()}.json") - if os.path.isfile(kind_file): - return kind_file + _resources.append(resource_dict) - LOGGER.error(f"{kind} schema not found, please run with `--update-schema`") - sys.exit(1) + return _resources def class_generator( @@ -460,8 +489,9 @@ def class_generator( overwrite: bool = False, dry_run: bool = False, output_file: str = "", + output_dir: str = "", add_tests: bool = False, -) -> str: +) -> List[str]: """ Generates a class for a given Kind. """ @@ -473,33 +503,37 @@ def class_generator( LOGGER.error(f"{kind} not found in {RESOURCES_MAPPING_FILE}, Please run with --update-schema") sys.exit(1) - resource_dict = parse_explain( - kind_schema_file=get_kind_schema_file(kind=kind), - namespaced=kind_and_namespaced_mappings["namespaced"], - ) - if not resource_dict: - return "" - - orig_filename, generated_py_file = generate_resource_file_from_dict( - resource_dict=resource_dict, - overwrite=overwrite, - dry_run=dry_run, - output_file=output_file, - add_tests=add_tests, - ) + resources = parse_explain(kind=kind) - if not dry_run: - run_command( - command=shlex.split(f"pre-commit run --files {generated_py_file}"), - verify_stderr=False, - check=False, + use_output_file_suffix: bool = len(resources) > 1 + generated_files: List[str] = [] + for resource_dict in resources: + output_file_suffix = resource_dict["group"].lower() if use_output_file_suffix else "" + + orig_filename, generated_py_file = generate_resource_file_from_dict( + resource_dict=resource_dict, + overwrite=overwrite, + dry_run=dry_run, + output_file=output_file, + add_tests=add_tests, + output_file_suffix=output_file_suffix, + output_dir=output_dir, ) - if orig_filename != generated_py_file and filecmp.cmp(orig_filename, generated_py_file): - LOGGER.warning(f"File {orig_filename} was not updated, deleting {generated_py_file}") - Path.unlink(Path(generated_py_file)) + if not dry_run: + run_command( + command=shlex.split(f"pre-commit run --files {generated_py_file}"), + verify_stderr=False, + check=False, + ) + + if orig_filename != generated_py_file and filecmp.cmp(orig_filename, generated_py_file): + LOGGER.warning(f"File {orig_filename} was not updated, deleting {generated_py_file}") + Path.unlink(Path(generated_py_file)) + + generated_files.append(generated_py_file) - return generated_py_file + return generated_files def write_and_format_rendered(filepath: str, data: str) -> None: diff --git a/class_generator/schema/__resources-mappings.json b/class_generator/schema/__resources-mappings.json index 9c759b6a19..fa5cd84b8c 100644 --- a/class_generator/schema/__resources-mappings.json +++ b/class_generator/schema/__resources-mappings.json @@ -1 +1 @@ -{"volumegroupreplication": {"namespaced": true}, "apiservice": {"namespaced": false}, "clusterpolicy": {"namespaced": false}, "deploymentconfig": {"namespaced": true}, "alertingrule": {"namespaced": true}, "machinehealthcheck": {"namespaced": true}, "cloudcredential": {"namespaced": false}, "volumesnapshot": {"namespaced": true}, "imagecontentpolicy": {"namespaced": false}, "infrastructure": {"namespaced": false}, "performanceprofile": {"namespaced": false}, "networkfence": {"namespaced": false}, "role": {"namespaced": false}, "selfnoderemediationtemplate": {"namespaced": true}, "subjectrulesreview": {"namespaced": true}, "storagesystem": {"namespaced": true}, "clusterrole": {"namespaced": false}, "imagepruner": {"namespaced": false}, "encryptionkeyrotationjob": {"namespaced": true}, "catalogsource": {"namespaced": true}, "migrationpolicy": {"namespaced": false}, "serviceca": {"namespaced": false}, "clusterserviceversion": {"namespaced": true}, "consoleplugin": {"namespaced": false}, "cephbucketnotification": {"namespaced": true}, "endpointslice": {"namespaced": true}, "nodenetworkconfigurationenactment": {"namespaced": false}, "olmconfig": {"namespaced": false}, "csidriver": {"namespaced": false}, "ippool": {"namespaced": true}, "selfsubjectreview": {"namespaced": false}, "metal3remediationtemplate": {"namespaced": true}, "cephblockpool": {"namespaced": true}, "user": {"namespaced": false}, "nmstate": {"namespaced": false}, "cephobjectzone": {"namespaced": true}, "virtualmachineexport": {"namespaced": true}, "virtualmachineinstance": {"namespaced": true}, "virtualmachinepreference": {"namespaced": true}, "machineconfig": {"namespaced": false}, "poddisruptionbudget": {"namespaced": true}, "reclaimspacecronjob": {"namespaced": true}, "probe": {"namespaced": true}, "service": {"namespaced": true}, "datasource": {"namespaced": true}, "bucketclass": {"namespaced": true}, "virtualmachinesnapshot": {"namespaced": true}, "horizontalpodautoscaler": {"namespaced": true}, "validatingadmissionpolicy": {"namespaced": false}, "nvidiadriver": {"namespaced": false}, "appliedclusterresourcequota": {"namespaced": true}, "cephcluster": {"namespaced": true}, "podtemplate": {"namespaced": true}, "build": {"namespaced": true}, "volumeuploadsource": {"namespaced": true}, "virtualmachine": {"namespaced": true}, "controllerrevision": {"namespaced": true}, "clusterversion": {"namespaced": false}, "ocsinitialization": {"namespaced": true}, "certificatesigningrequest": {"namespaced": false}, "volumereplicationclass": {"namespaced": false}, "egressservice": {"namespaced": true}, "route": {"namespaced": true}, "adminpolicybasedexternalroute": {"namespaced": false}, "runtimeclass": {"namespaced": false}, "credentialsrequest": {"namespaced": true}, "lease": {"namespaced": true}, "nodefeaturerule": {"namespaced": true}, "objectbucket": {"namespaced": false}, "storagecluster": {"namespaced": true}, "localsubjectaccessreview": {"namespaced": false}, "cephobjectzonegroup": {"namespaced": true}, "serviceaccount": {"namespaced": true}, "etcd": {"namespaced": false}, "overlappingrangeipreservation": {"namespaced": true}, "identity": {"namespaced": false}, "rolebinding": {"namespaced": false}, "metal3remediation": {"namespaced": true}, "clusterrolebinding": {"namespaced": false}, "localvolumeset": {"namespaced": true}, "machineconfiguration": {"namespaced": false}, "endpoints": {"namespaced": true}, "subscription": {"namespaced": true}, "storage": {"namespaced": false}, "cephfilesystemmirror": {"namespaced": true}, "ingresscontroller": {"namespaced": true}, "cephnfs": {"namespaced": true}, "localresourceaccessreview": {"namespaced": true}, "selfsubjectaccessreview": {"namespaced": false}, "buildconfig": {"namespaced": true}, "consoleyamlsample": {"namespaced": false}, "namespacestore": {"namespaced": true}, "consolelink": {"namespaced": false}, "encryptionkeyrotationcronjob": {"namespaced": true}, "namespace": {"namespaced": false}, "volumesnapshotcontent": {"namespaced": false}, "dnsrecord": {"namespaced": true}, "replicaset": {"namespaced": true}, "egressqos": {"namespaced": true}, "recipe": {"namespaced": true}, "featuregate": {"namespaced": false}, "kubescheduler": {"namespaced": false}, "controlplanemachineset": {"namespaced": true}, "clusteroperator": {"namespaced": false}, "cephbuckettopic": {"namespaced": true}, "packagemanifest": {"namespaced": true}, "volumeattachment": {"namespaced": false}, "egressrouter": {"namespaced": true}, "helmchartrepository": {"namespaced": false}, "authentication": {"namespaced": false}, "controllerconfig": {"namespaced": false}, "image": {"namespaced": false}, "kubeapiserver": {"namespaced": false}, "cephfilesystemsubvolumegroup": {"namespaced": true}, "egressip": {"namespaced": false}, "baremetalhost": {"namespaced": true}, "virtualmachineclone": {"namespaced": true}, "noobaaaccount": {"namespaced": true}, "machineset": {"namespaced": true}, "ipaddressclaim": {"namespaced": true}, "imagedigestmirrorset": {"namespaced": false}, "prometheus": {"namespaced": true}, "operatorgroup": {"namespaced": true}, "hostpathprovisioner": {"namespaced": false}, "dns": {"namespaced": false}, "ssp": {"namespaced": true}, "openshiftcontrollermanager": {"namespaced": false}, "oauthauthorizetoken": {"namespaced": false}, "templateinstance": {"namespaced": true}, "projecthelmchartrepository": {"namespaced": true}, "project": {"namespaced": false}, "podnetworkconnectivitycheck": {"namespaced": true}, "apiserver": {"namespaced": false}, "podsecuritypolicyreview": {"namespaced": true}, "cephfilesystem": {"namespaced": true}, "backingstore": {"namespaced": true}, "insightsoperator": {"namespaced": false}, "ovirtvolumepopulator": {"namespaced": true}, "dataimage": {"namespaced": true}, "volumegroupreplicationclass": {"namespaced": false}, "priorityclass": {"namespaced": false}, "nodehealthcheck": {"namespaced": false}, "alertmanager": {"namespaced": true}, "adminnetworkpolicy": {"namespaced": false}, "pod": {"namespaced": true}, "virtualmachinepool": {"namespaced": true}, "storageclaim": {"namespaced": false}, "kubevirt": {"namespaced": true}, "virtualmachineinstancepreset": {"namespaced": true}, "installplan": {"namespaced": true}, "imagestreamimage": {"namespaced": true}, "imagestreamtag": {"namespaced": true}, "hostfirmwaresettings": {"namespaced": true}, "volumereplication": {"namespaced": true}, "replicationcontroller": {"namespaced": true}, "fenceagentsremediationtemplate": {"namespaced": true}, "statefulset": {"namespaced": true}, "validatingadmissionpolicybinding": {"namespaced": false}, "useroauthaccesstoken": {"namespaced": false}, "operatorpki": {"namespaced": true}, "ingress": {"namespaced": true}, "customresourcedefinition": {"namespaced": false}, "brokertemplateinstance": {"namespaced": false}, "kubestorageversionmigrator": {"namespaced": false}, "csinode": {"namespaced": false}, "deployment": {"namespaced": true}, "imagetagmirrorset": {"namespaced": false}, "objecttransfer": {"namespaced": false}, "customresourcedefinitions": {"namespaced": false}, "nodefeature": {"namespaced": true}, "ingressclass": {"namespaced": false}, "imagetag": {"namespaced": true}, "hardwaredata": {"namespaced": true}, "datavolume": {"namespaced": true}, "config": {"namespaced": false}, "proxy": {"namespaced": false}, "imagestream": {"namespaced": true}, "group": {"namespaced": false}, "imagecontentsourcepolicy": {"namespaced": false}, "cephblockpoolradosnamespace": {"namespaced": true}, "csistoragecapacity": {"namespaced": true}, "volumeimportsource": {"namespaced": true}, "oauthclient": {"namespaced": false}, "selfnoderemediation": {"namespaced": true}, "podsecuritypolicysubjectreview": {"namespaced": true}, "virtualmachinerestore": {"namespaced": true}, "firmwareschema": {"namespaced": true}, "oauthaccesstoken": {"namespaced": false}, "networkpolicy": {"namespaced": true}, "scheduler": {"namespaced": false}, "containerruntimeconfig": {"namespaced": false}, "virtualmachineinstancemigration": {"namespaced": true}, "consolenotification": {"namespaced": false}, "storageprofile": {"namespaced": true}, "cephcosidriver": {"namespaced": true}, "machine": {"namespaced": true}, "storageclient": {"namespaced": false}, "daemonset": {"namespaced": true}, "storageclass": {"namespaced": false}, "hostfirmwarecomponents": {"namespaced": true}, "imagestreammapping": {"namespaced": true}, "rangeallocation": {"namespaced": false}, "virtualmachineinstancereplicaset": {"namespaced": true}, "operatorhub": {"namespaced": false}, "persistentvolume": {"namespaced": false}, "imagestreamimport": {"namespaced": true}, "rolebindingrestriction": {"namespaced": true}, "alertmanagerconfig": {"namespaced": true}, "storageclusterpeer": {"namespaced": true}, "volumeclonesource": {"namespaced": true}, "servicemonitor": {"namespaced": true}, "egressfirewall": {"namespaced": true}, "secret": {"namespaced": true}, "noderesourcetopology": {"namespaced": false}, "localvolume": {"namespaced": true}, "subjectaccessreview": {"namespaced": false}, "event": {"namespaced": false}, "consolesample": {"namespaced": false}, "hyperconverged": {"namespaced": true}, "nodemetrics": {"namespaced": false}, "kubeletconfig": {"namespaced": false}, "consoleexternalloglink": {"namespaced": false}, "baselineadminnetworkpolicy": {"namespaced": false}, "consoleclidownload": {"namespaced": false}, "openstackvolumepopulator": {"namespaced": true}, "objectbucketclaim": {"namespaced": true}, "podsecuritypolicyselfsubjectreview": {"namespaced": true}, "selfsubjectrulesreview": {"namespaced": true}, "fenceagentsremediation": {"namespaced": true}, "clusterautoscaler": {"namespaced": false}, "prioritylevelconfiguration": {"namespaced": false}, "kubecontrollermanager": {"namespaced": false}, "configmap": {"namespaced": true}, "clusterresourcequota": {"namespaced": false}, "oauthclientauthorization": {"namespaced": false}, "openshiftapiserver": {"namespaced": false}, "cephclient": {"namespaced": true}, "thanosruler": {"namespaced": true}, "validatingwebhookconfiguration": {"namespaced": false}, "oauth": {"namespaced": false}, "cephobjectrealm": {"namespaced": true}, "clustercsidriver": {"namespaced": false}, "preprovisioningimage": {"namespaced": true}, "useridentitymapping": {"namespaced": false}, "tuned": {"namespaced": true}, "dataimportcron": {"namespaced": true}, "profile": {"namespaced": true}, "tokenreview": {"namespaced": false}, "node": {"namespaced": false}, "machineconfigpool": {"namespaced": false}, "network": {"namespaced": false}, "operator": {"namespaced": false}, "resourcequota": {"namespaced": true}, "virtualmachinesnapshotcontent": {"namespaced": true}, "cdi": {"namespaced": false}, "alertrelabelconfig": {"namespaced": true}, "console": {"namespaced": false}, "template": {"namespaced": false}, "mutatingwebhookconfiguration": {"namespaced": false}, "nodenetworkstate": {"namespaced": false}, "projectrequest": {"namespaced": false}, "virtualmachineclusterinstancetype": {"namespaced": false}, "volumegroupreplicationcontent": {"namespaced": false}, "flowschema": {"namespaced": false}, "podmonitor": {"namespaced": true}, "apirequestcount": {"namespaced": false}, "limitrange": {"namespaced": true}, "localvolumediscoveryresult": {"namespaced": true}, "prometheusrule": {"namespaced": true}, "provisioning": {"namespaced": false}, "aaq": {"namespaced": false}, "job": {"namespaced": true}, "consolequickstart": {"namespaced": false}, "networkaddonsconfig": {"namespaced": false}, "storageversionmigration": {"namespaced": false}, "binding": {"namespaced": true}, "volumesnapshotclass": {"namespaced": false}, "cronjob": {"namespaced": true}, "csiaddonsnode": {"namespaced": true}, "virtualmachineclusterpreference": {"namespaced": false}, "cdiconfig": {"namespaced": false}, "podmetrics": {"namespaced": true}, "persistentvolumeclaim": {"namespaced": true}, "storagerequest": {"namespaced": true}, "storageconsumer": {"namespaced": true}, "componentstatus": {"namespaced": false}, "cephobjectstoreuser": {"namespaced": true}, "reclaimspacejob": {"namespaced": true}, "csisnapshotcontroller": {"namespaced": false}, "imagesignature": {"namespaced": false}, "localvolumediscovery": {"namespaced": true}, "securitycontextconstraints": {"namespaced": false}, "cephobjectstore": {"namespaced": true}, "bmceventsubscription": {"namespaced": true}, "ipaddress": {"namespaced": true}, "selfnoderemediationconfig": {"namespaced": true}, "operatorcondition": {"namespaced": true}, "virtualmachineinstancetype": {"namespaced": true}, "nodefeaturediscovery": {"namespaced": true}, "machineautoscaler": {"namespaced": true}, "nodenetworkconfigurationpolicy": {"namespaced": false}, "noobaa": {"namespaced": true}, "cephrbdmirror": {"namespaced": true}, "storagestate": {"namespaced": false}} \ No newline at end of file +{"machineconfiguration": [{"description": "MachineConfiguration provides information to configure an operator to manage Machine Configuration. \n Compatibility level 1: Stable within a major release for a minimum of 12 months or 3 minor releases (whichever is longer).", "type": "object", "required": ["spec"], "properties": {"apiVersion": {"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", "type": "string"}, "kind": {"description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "type": "string"}, "metadata": {"description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"}, "spec": {"description": "spec is the specification of the desired behavior of the Machine Config Operator", "type": "object", "properties": {"failedRevisionLimit": {"description": "failedRevisionLimit is the number of failed static pod installer revisions to keep on disk and in the api -1 = unlimited, 0 or unset = 5 (default)", "type": "integer", "format": "int32"}, "forceRedeploymentReason": {"description": "forceRedeploymentReason can be used to force the redeployment of the operand by providing a unique string. This provides a mechanism to kick a previously failed deployment and provide a reason why you think it will work this time instead of failing again on the same config.", "type": "string"}, "logLevel": {"description": "logLevel is an intent based logging for an overall component. It does not give fine grained control, but it is a simple way to manage coarse grained logging choices that operators have to interpret for their operands. \n Valid values are: \"Normal\", \"Debug\", \"Trace\", \"TraceAll\". Defaults to \"Normal\".", "type": "string", "enum": ["", "Normal", "Debug", "Trace", "TraceAll"]}, "managementState": {"description": "managementState indicates whether and how the operator should manage the component", "type": "string", "pattern": "^(Managed|Unmanaged|Force|Removed)$"}, "observedConfig": {"description": "observedConfig holds a sparse config that controller has observed from the cluster state. It exists in spec because it is an input to the level for the operator", "x-kubernetes-preserve-unknown-fields": true}, "operatorLogLevel": {"description": "operatorLogLevel is an intent based logging for the operator itself. It does not give fine grained control, but it is a simple way to manage coarse grained logging choices that operators have to interpret for themselves. \n Valid values are: \"Normal\", \"Debug\", \"Trace\", \"TraceAll\". Defaults to \"Normal\".", "type": "string", "enum": ["", "Normal", "Debug", "Trace", "TraceAll"]}, "succeededRevisionLimit": {"description": "succeededRevisionLimit is the number of successful static pod installer revisions to keep on disk and in the api -1 = unlimited, 0 or unset = 5 (default)", "type": "integer", "format": "int32"}, "unsupportedConfigOverrides": {"description": "unsupportedConfigOverrides overrides the final configuration that was computed by the operator. Red Hat does not support the use of this field. Misuse of this field could lead to unexpected behavior or conflict with other configuration options. Seek guidance from the Red Hat support before using this field. Use of this property blocks cluster upgrades, it must be removed before upgrading your cluster.", "x-kubernetes-preserve-unknown-fields": true}}}, "status": {"description": "status is the most recently observed status of the Machine Config Operator", "type": "object", "properties": {"conditions": {"description": "conditions is a list of conditions and their status", "type": "array", "items": {"description": "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }", "type": "object", "required": ["lastTransitionTime", "message", "reason", "status", "type"], "properties": {"lastTransitionTime": {"description": "lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", "type": "string", "format": "date-time"}, "message": {"description": "message is a human readable message indicating details about the transition. This may be an empty string.", "type": "string", "maxLength": 32768}, "observedGeneration": {"description": "observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance.", "type": "integer", "format": "int64", "minimum": 0}, "reason": {"description": "reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty.", "type": "string", "maxLength": 1024, "minLength": 1, "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$"}, "status": {"description": "status of the condition, one of True, False, Unknown.", "type": "string", "enum": ["True", "False", "Unknown"]}, "type": {"description": "type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)", "type": "string", "maxLength": 316, "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$"}}}, "x-kubernetes-list-map-keys": ["type"], "x-kubernetes-list-type": "map"}, "observedGeneration": {"description": "observedGeneration is the last generation change you've dealt with", "type": "integer", "format": "int64"}}}}, "x-kubernetes-group-version-kind": [{"group": "operator.openshift.io", "kind": "MachineConfiguration", "version": "v1"}], "namespaced": false}], "kubestorageversionmigrator": [{"description": "KubeStorageVersionMigrator provides information to configure an operator to manage kube-storage-version-migrator. \n Compatibility level 1: Stable within a major release for a minimum of 12 months or 3 minor releases (whichever is longer).", "type": "object", "required": ["spec"], "properties": {"apiVersion": {"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", "type": "string"}, "kind": {"description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "type": "string"}, "metadata": {"description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"}, "spec": {"type": "object", "properties": {"logLevel": {"description": "logLevel is an intent based logging for an overall component. It does not give fine grained control, but it is a simple way to manage coarse grained logging choices that operators have to interpret for their operands. \n Valid values are: \"Normal\", \"Debug\", \"Trace\", \"TraceAll\". Defaults to \"Normal\".", "type": "string", "enum": ["", "Normal", "Debug", "Trace", "TraceAll"]}, "managementState": {"description": "managementState indicates whether and how the operator should manage the component", "type": "string", "pattern": "^(Managed|Unmanaged|Force|Removed)$"}, "observedConfig": {"description": "observedConfig holds a sparse config that controller has observed from the cluster state. It exists in spec because it is an input to the level for the operator", "x-kubernetes-preserve-unknown-fields": true}, "operatorLogLevel": {"description": "operatorLogLevel is an intent based logging for the operator itself. It does not give fine grained control, but it is a simple way to manage coarse grained logging choices that operators have to interpret for themselves. \n Valid values are: \"Normal\", \"Debug\", \"Trace\", \"TraceAll\". Defaults to \"Normal\".", "type": "string", "enum": ["", "Normal", "Debug", "Trace", "TraceAll"]}, "unsupportedConfigOverrides": {"description": "unsupportedConfigOverrides overrides the final configuration that was computed by the operator. Red Hat does not support the use of this field. Misuse of this field could lead to unexpected behavior or conflict with other configuration options. Seek guidance from the Red Hat support before using this field. Use of this property blocks cluster upgrades, it must be removed before upgrading your cluster.", "x-kubernetes-preserve-unknown-fields": true}}}, "status": {"type": "object", "properties": {"conditions": {"description": "conditions is a list of conditions and their status", "type": "array", "items": {"description": "OperatorCondition is just the standard condition fields.", "type": "object", "properties": {"lastTransitionTime": {"type": "string", "format": "date-time"}, "message": {"type": "string"}, "reason": {"type": "string"}, "status": {"type": "string"}, "type": {"type": "string"}}}}, "generations": {"description": "generations are used to determine when an item needs to be reconciled or has changed in a way that needs a reaction.", "type": "array", "items": {"description": "GenerationStatus keeps track of the generation for a given resource so that decisions about forced updates can be made.", "type": "object", "properties": {"group": {"description": "group is the group of the thing you're tracking", "type": "string"}, "hash": {"description": "hash is an optional field set for resources without generation that are content sensitive like secrets and configmaps", "type": "string"}, "lastGeneration": {"description": "lastGeneration is the last generation of the workload controller involved", "type": "integer", "format": "int64"}, "name": {"description": "name is the name of the thing you're tracking", "type": "string"}, "namespace": {"description": "namespace is where the thing you're tracking is", "type": "string"}, "resource": {"description": "resource is the resource type of the thing you're tracking", "type": "string"}}}}, "observedGeneration": {"description": "observedGeneration is the last generation change you've dealt with", "type": "integer", "format": "int64"}, "readyReplicas": {"description": "readyReplicas indicates how many replicas are ready and at the desired state", "type": "integer", "format": "int32"}, "version": {"description": "version is the level this availability applies to", "type": "string"}}}}, "x-kubernetes-group-version-kind": [{"group": "operator.openshift.io", "kind": "KubeStorageVersionMigrator", "version": "v1"}], "namespaced": false}], "clusterautoscaler": [{"description": "ClusterAutoscaler is the Schema for the clusterautoscalers API", "type": "object", "properties": {"apiVersion": {"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", "type": "string"}, "kind": {"description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", "type": "string"}, "metadata": {"description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"}, "spec": {"description": "Desired state of ClusterAutoscaler resource", "type": "object", "properties": {"balanceSimilarNodeGroups": {"description": "BalanceSimilarNodeGroups enables/disables the `--balance-similar-node-groups` cluster-autoscaler feature. This feature will automatically identify node groups with the same instance type and the same set of labels and try to keep the respective sizes of those node groups balanced.", "type": "boolean"}, "balancingIgnoredLabels": {"description": "BalancingIgnoredLabels sets \"--balancing-ignore-label