Coverage for class_generator/parsers/explain_parser.py: 94%

66 statements  

« prev     ^ index     » next       coverage.py v7.10.1, created at 2025-07-29 12:31 +0300

1"""Parser for OpenAPI explain data to extract resource information.""" 

2 

3from typing import Any 

4 

5from simple_logger.logger import get_logger 

6 

7from class_generator.constants import MISSING_DESCRIPTION_STR 

8from class_generator.core.schema import extract_group_kind_version, read_resources_mapping_file 

9from class_generator.parsers.type_parser import get_property_schema, prepare_property_dict 

10from class_generator.utils import get_latest_version 

11from ocp_resources.resource import Resource 

12 

13LOGGER = get_logger(name=__name__) 

14 

15 

16def parse_explain(kind: str) -> list[dict[str, Any]]: 

17 """ 

18 Parse OpenAPI explain data for a given resource kind. 

19  

20 Args: 

21 kind: The Kubernetes resource kind 

22  

23 Returns: 

24 List of resource dictionaries with parsed information 

25 """ 

26 _schema_definition = read_resources_mapping_file() 

27 _resources: list[dict[str, Any]] = [] 

28 

29 _kinds_schema = _schema_definition[kind.lower()] 

30 

31 # Group schemas by API group 

32 schemas_by_group: dict[str, list[dict[str, Any]]] = {} 

33 for schema in _kinds_schema: 

34 gvk_list = schema.get("x-kubernetes-group-version-kind", []) 

35 if gvk_list: 

36 group = gvk_list[0].get("group", "") 

37 if group not in schemas_by_group: 

38 schemas_by_group[group] = [] 

39 schemas_by_group[group].append(schema) 

40 

41 # For each API group, select the latest version 

42 filtered_schemas = [] 

43 for group, group_schemas in schemas_by_group.items(): 

44 if len(group_schemas) > 1: 

45 # Multiple versions in same group - pick latest 

46 versions = [] 

47 for schema in group_schemas: 

48 gvk_list = schema.get("x-kubernetes-group-version-kind", []) 

49 if gvk_list: 

50 version = gvk_list[0].get("version", "") 

51 versions.append(version) 

52 

53 latest_version = get_latest_version(versions=versions) 

54 

55 # Add only the schema with the latest version 

56 for schema in group_schemas: 

57 gvk_list = schema.get("x-kubernetes-group-version-kind", []) 

58 if gvk_list and gvk_list[0].get("version") == latest_version: 

59 filtered_schemas.append(schema) 

60 break 

61 else: 

62 # Single version in this group 

63 filtered_schemas.extend(group_schemas) 

64 

65 # Use filtered schemas instead of all schemas 

66 for _kind_schema in filtered_schemas: 

67 namespaced = _kind_schema["namespaced"] 

68 resource_dict: dict[str, Any] = { 

69 "base_class": "NamespacedResource" if namespaced else "Resource", 

70 "description": _kind_schema.get("description", MISSING_DESCRIPTION_STR), 

71 "fields": [], 

72 "spec": [], 

73 } 

74 

75 schema_properties: dict[str, Any] = _kind_schema.get("properties", {}) 

76 fields_required = _kind_schema.get("required", []) 

77 

78 resource_dict.update(extract_group_kind_version(_kind_schema=_kind_schema)) 

79 

80 if spec_schema := schema_properties.get("spec", {}): 

81 spec_schema = get_property_schema(property_=spec_schema) 

82 spec_required = spec_schema.get("required", []) 

83 resource_dict = prepare_property_dict( 

84 schema=spec_schema.get("properties", {}), 

85 required=spec_required, 

86 resource_dict=resource_dict, 

87 dict_key="spec", 

88 ) 

89 

90 resource_dict = prepare_property_dict( 

91 schema=schema_properties, 

92 required=fields_required, 

93 resource_dict=resource_dict, 

94 dict_key="fields", 

95 ) 

96 

97 api_group_real_name = resource_dict.get("group") 

98 # Store the original group name before converting 

99 resource_dict["original_group"] = api_group_real_name 

100 

101 # If API Group is not present in resource, try to get it from VERSION 

102 if not api_group_real_name: 

103 version_splited = resource_dict["version"].split("/") 

104 if len(version_splited) == 2: 

105 api_group_real_name = version_splited[0] 

106 resource_dict["original_group"] = api_group_real_name 

107 

108 if api_group_real_name: 

109 api_group_for_resource_api_group = api_group_real_name.upper().replace(".", "_").replace("-", "_") 

110 resource_dict["group"] = api_group_for_resource_api_group 

111 missing_api_group_in_resource: bool = not hasattr(Resource.ApiGroup, api_group_for_resource_api_group) 

112 

113 if missing_api_group_in_resource: 

114 LOGGER.warning( 

115 f"Missing API Group in Resource\n" 

116 f"Please add `Resource.ApiGroup.{api_group_for_resource_api_group} = {api_group_real_name}` " 

117 "manually into ocp_resources/resource.py under Resource class > ApiGroup class." 

118 ) 

119 

120 else: 

121 api_version_for_resource_api_version = resource_dict["version"].upper() 

122 missing_api_version_in_resource: bool = not hasattr( 

123 Resource.ApiVersion, api_version_for_resource_api_version 

124 ) 

125 

126 if missing_api_version_in_resource: 

127 LOGGER.warning( 

128 f"Missing API Version in Resource\n" 

129 f"Please add `Resource.ApiVersion.{api_version_for_resource_api_version} = {resource_dict['version']}` " 

130 "manually into ocp_resources/resource.py under Resource class > ApiGroup class." 

131 ) 

132 

133 _resources.append(resource_dict) 

134 

135 return _resources