Skip to content
This repository has been archived by the owner on Apr 10, 2024. It is now read-only.

Commit

Permalink
feat: cover image.metadef schemas
Browse files Browse the repository at this point in the history
This is a relatively big change since metadefs themselves are not
trivial.

- rework response_key handling
- rework subtypes conflict resolution
- add new cli command type "list_from_struct" which allows building a
  "list" command for API that return object (it does not seem reasonable
  to alter data in SDK for that since it forces some assumptions about
  the data)
- de-duplicate "domain" schema in identity.auth.token to make use of new
  subtype naming
  • Loading branch information
gtema committed Mar 14, 2024
1 parent 5e400be commit aa7445a
Show file tree
Hide file tree
Showing 17 changed files with 515 additions and 263 deletions.
27 changes: 17 additions & 10 deletions codegenerator/common/rust.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,7 @@ def get_local_attribute_name(self, name: str) -> str:
attr_name = "_".join(
x.lower() for x in re.split(common.SPLIT_NAME_RE, name)
)
if attr_name in ["type", "self", "enum", "ref"]:
if attr_name in ["type", "self", "enum", "ref", "default"]:
attr_name = f"_{attr_name}"
return attr_name

Expand Down Expand Up @@ -812,19 +812,24 @@ def set_models(self, models):
self.models = models
self.refs = {}
self.ignored_models = []
unique_model_names: set[str] = set()
# A dictionary of model names to references to assign unique names
unique_models: dict[str, model.Reference] = {}
for model_ in models:
model_data_type = self.convert_model(model_)
if not isinstance(model_data_type, BaseCompoundType):
continue
name = getattr(model_data_type, "name", None)
if name and name in unique_model_names:
if (
name
and name in unique_models
and unique_models[name] != model_.reference
):
# There is already a model with this name. Try adding suffix from datatype name
new_name = name + model_data_type.__class__.__name__
if new_name not in unique_model_names:
if new_name not in unique_models:
# New name is still unused
model_data_type.name = new_name
unique_model_names.add(new_name)
unique_models[new_name] = model_.reference
elif isinstance(model_data_type, Struct):
# This is already an exceptional case (identity.mapping
# with remote being oneOf with multiple structs)
Expand All @@ -833,7 +838,7 @@ def set_models(self, models):
new_new_name = name + "".join(
x.title() for x in props
).replace("_", "")
if new_new_name not in unique_model_names:
if new_new_name not in unique_models:
for other_ref, other_model in self.refs.items():
other_name = getattr(other_model, "name", None)
if not other_name:
Expand All @@ -848,20 +853,22 @@ def set_models(self, models):
x.title() for x in props
).replace("_", "")
other_model.name = new_other_name
unique_model_names.add(new_other_name)
unique_models[new_other_name] = (
model_.reference
)

model_data_type.name = new_new_name
unique_model_names.add(new_new_name)
unique_models[new_new_name] = model_.reference
else:
raise RuntimeError(
"Model name %s is already present" % new_name
"Model name %s is already present" % new_new_name
)
else:
raise RuntimeError(
"Model name %s is already present" % new_name
)
elif name:
unique_model_names.add(name)
unique_models[name] = model_.reference

for ignore_model in self.ignored_models:
self.discard_model(ignore_model)
Expand Down
17 changes: 17 additions & 0 deletions codegenerator/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,23 @@ def post_process_image_operation(
if resource_name.startswith("schema"):
# Image schemas are a JSON operation
operation.targets["rust-cli"].operation_type = "json"
elif resource_name == "metadef/namespace" and operation_name != "list":
operation.targets["rust-sdk"].response_key = "null"
operation.targets["rust-cli"].response_key = "null"
elif (
resource_name == "metadef/namespace/property"
and operation_name == "list"
):
operation.targets["rust-cli"].operation_type = "list_from_struct"
operation.targets["rust-cli"].response_key = "properties"
operation.targets["rust-sdk"].response_key = "properties"
elif resource_name == "metadef/namespace/resource_type":
operation.targets["rust-cli"].response_key = (
"resource_type_associations"
)
operation.targets["rust-sdk"].response_key = (
"resource_type_associations"
)

return operation

Expand Down
34 changes: 22 additions & 12 deletions codegenerator/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,8 @@ def parse_schema(
ignore_read_only=ignore_read_only,
)
if schema == {}:
return PrimitiveNull()
# `{}` is `Any` according to jsonschema
return PrimitiveAny()
if not type_ and "format" in schema:
return ConstraintString(**schema)
raise RuntimeError("Cannot determine type for %s", schema)
Expand Down Expand Up @@ -442,17 +443,26 @@ def parse_object(
if x.reference
]
):
# Structure with the same name is already present. Prefix the
# new one with the parent name
if parent_name and name:
new_name = parent_name + "_" + name

if Reference(name=new_name, type=obj.reference.type) in [
x.reference for x in results
]:
raise NotImplementedError
else:
obj.reference.name = new_name
if obj.reference in [
x.reference for x in results if x.reference
]:
# This is already same object - we have luck and can
# de-duplicate structures. It is at the moment the case in
# `image.metadef.namespace` with absolutely same `items`
# object present few times
pass
else:
# Structure with the same name is already present. Prefix the
# new one with the parent name
if parent_name and name:
new_name = parent_name + "_" + name

if Reference(
name=new_name, type=obj.reference.type
) in [x.reference for x in results]:
raise NotImplementedError
else:
obj.reference.name = new_name
results.append(obj)
return obj

Expand Down
74 changes: 66 additions & 8 deletions codegenerator/openapi/glance.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,12 @@
class GlanceGenerator(OpenStackServerSourceBase):
URL_TAG_MAP = {
"/versions": "version",
"/metadefs/resource_types": "metadata-definition-resource-types",
"/metadefs/namespaces/{namespace_name}/resource_types": "metadata-definition-resource-types",
"/metadefs/namespaces/{namespace_name}/properties": "metadata-definition-properties",
"/metadefs/namespaces/{namespace_name}/objects": "metadata-definition-objects",
"/metadefs/namespaces/{namespace_name}/tags": "metadata-definition-tags",
"/metadefs/namespaces": "metadata-definition-namespaces",
}

def __init__(self):
Expand Down Expand Up @@ -314,6 +320,13 @@ def _generate(self, target_dir, args):
for route in self.router.map.matchlist:
if not route.conditions:
continue
# Hack the image metadef namespace url to have namespace_name param
# instead of namespace due to the presence of "namespace" parameter
# also in the body
if route.routepath.startswith("/metadefs/namespaces/"):
for part in route.routelist:
if isinstance(part, dict) and part["name"] == "namespace":
part["name"] = "namespace_name"
self._process_route(route, openapi_spec, ver_prefix="/v2")

self._sanitize_param_ver_info(openapi_spec, self.min_api_version)
Expand Down Expand Up @@ -613,6 +626,46 @@ def _get_schema_ref(
ref = f"#/components/schemas/{name}"
elif name in [
"MetadefsResource_TypesListResponse",
]:
openapi_spec.components.schemas.setdefault(
name,
TypeSchema(
**{
"type": "object",
"description": "A list of abbreviated resource type JSON objects, where each object contains the name of the resource type and its created_at and updated_at timestamps in ISO 8601 Format.",
"properties": {
"resource_types": {
"type": "array",
"items": {
"type": "object",
"description": "Resource type",
"properties": {
"name": {
"type": "string",
"description": "Resource type name",
},
"created_at": {
"type": "string",
"format": "date-time",
"description": "Resource type creation date",
"readOnly": True,
},
"updated_at": {
"type": "string",
"format": "date-time",
"description": "Resource type update date",
"readOnly": True,
},
},
},
}
},
}
),
)
ref = f"#/components/schemas/{name}"
elif name in [
"MetadefsNamespacesResource_TypesShowResponse",
]:
openapi_spec.components.schemas.setdefault(
name,
Expand Down Expand Up @@ -689,14 +742,19 @@ def _get_glance_schema(self, schema, name: str | None = None):
# List of image props that are by default integer, but in real life
# are surely going i64 side
i32_fixes = ["size", "virtual_size"]
if name and name == "ImagesListResponse":
for field in i32_fixes:
res["properties"]["images"]["items"]["properties"][field][
"format"
] = "int64"
if name and name == "ImageShowResponse":
for field in i32_fixes:
res["properties"][field]["format"] = "int64"
if name:
if name == "ImagesListResponse":
for field in i32_fixes:
res["properties"]["images"]["items"]["properties"][field][
"format"
] = "int64"
elif name == "ImageShowResponse":
for field in i32_fixes:
res["properties"][field]["format"] = "int64"
elif name == "MetadefsNamespacesPropertiesListResponse":
res["properties"]["properties"]["additionalProperties"][
"type"
] = "object"
return TypeSchema(**res)

@classmethod
Expand Down
Loading

0 comments on commit aa7445a

Please sign in to comment.