Skip to content

Commit c71f8e6

Browse files
committed
APM-1556 Include api_guid in product attributes
Mark meta.api.id as deprecated. Prefer meta.api.guid.
1 parent 6cae835 commit c71f8e6

6 files changed

Lines changed: 153 additions & 51 deletions

File tree

ansible/collections/ansible_collections/nhsd/apigee/plugins/module_utils/models/apigee/product.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,48 @@
33

44

55
class ApigeeProductAttribute(pydantic.BaseModel):
6-
name: pydantic.constr(regex=r"^(?!(access|ratelimit|spec_guid)$)")
6+
name: pydantic.constr(regex=r"^(?!(access|ratelimit|api_guid|api_spec_guid)$)")
77
value: str
88

99

10-
class ApigeeProductAttributeAccess(ApigeeProductAttribute):
10+
class ApigeeProductAttributeAccess(pydantic.BaseModel):
1111
name: typing.Literal["access"]
1212
value: typing.Literal["public", "private"]
1313

1414

15-
class ApigeeProductAttributeRateLimit(ApigeeProductAttribute):
15+
class ApigeeProductAttributeRateLimit(pydantic.BaseModel):
1616
name: typing.Literal["ratelimit"]
1717
value: pydantic.constr(regex=r"^[0-9]+(ps|pm)$")
1818

1919

20-
class ApigeeProductAttributeSpecGuid(ApigeeProductAttribute):
21-
name: typing.Literal["spec_guid"]
22-
value: pydantic.UUID4
20+
class StringValueMixin:
21+
"""Convert a non-string value attribute to string on export."""
2322

2423
def dict(self, **kwargs):
2524
native = super().dict(**kwargs)
2625
native.update({"value": str(native["value"])})
2726
return native
2827

2928

29+
class ApigeeProductAttributeApiSpecGuid(StringValueMixin, pydantic.BaseModel):
30+
name: typing.Literal["api_spec_guid"]
31+
value: pydantic.UUID4
32+
33+
34+
class ApigeeProductAttributeApiGuid(StringValueMixin, pydantic.BaseModel):
35+
name: typing.Literal["api_guid"]
36+
value: pydantic.UUID4
37+
38+
3039
class ApigeeProduct(pydantic.BaseModel):
3140
name: str
3241
approvalType: typing.Literal["auto", "manual"]
3342
attributes: typing.List[
3443
typing.Union[
3544
ApigeeProductAttributeAccess,
3645
ApigeeProductAttributeRateLimit,
37-
ApigeeProductAttributeSpecGuid,
46+
ApigeeProductAttributeApiSpecGuid,
47+
ApigeeProductAttributeApiGuid,
3848
ApigeeProductAttribute,
3949
],
4050
]
Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,23 @@
11
import pydantic
22

3-
from ansible_collections.nhsd.apigee.plugins.module_utils.models.manifest.apigee import ManifestApigee
4-
from ansible_collections.nhsd.apigee.plugins.module_utils.models.manifest.meta import ManifestMeta
3+
from ansible_collections.nhsd.apigee.plugins.module_utils.models.manifest.apigee import (
4+
ManifestApigee,
5+
)
6+
from ansible_collections.nhsd.apigee.plugins.module_utils.models.manifest.meta import (
7+
ManifestMeta,
8+
)
9+
from ansible_collections.nhsd.apigee.plugins.module_utils.models.apigee.product import (
10+
ApigeeProductAttributeApiSpecGuid,
11+
ApigeeProductAttributeApiGuid,
12+
)
13+
14+
15+
def get_attrs(product, cls):
16+
return [
17+
attr
18+
for attr in product.attributes
19+
if isinstance(attr, cls)
20+
]
521

622

723
class Manifest(pydantic.BaseModel):
@@ -17,16 +33,45 @@ def validate_apigee_spec_guids(cls, meta, values):
1733

1834
for env in apigee.environments:
1935
for product in env.products:
20-
spec_guid_attrs = [attr for attr in product.attributes if attr.name == "spec_guid"]
21-
if meta.api.spec_guids is None and len(spec_guid_attrs) != 0:
36+
guid_attrs = get_attrs(product, ApigeeProductAttributeApiGuid)
37+
if len(guid_attrs) > 1 or (
38+
len(guid_attrs) == 1 and guid_attrs[0].value != meta.api.guid
39+
):
2240
raise AssertionError(
23-
f"product {product.name} has a spec_guid attribute when spec_guids are not defined in meta.api block"
41+
f"product '{product.name}' attributes must "
42+
+ f"contain api_guid = '{meta.api.guid}'"
43+
)
44+
elif len(guid_attrs) == 0:
45+
product.attributes.append(
46+
ApigeeProductAttributeApiGuid(
47+
name="api_guid",
48+
value=meta.api.guid
49+
)
2450
)
51+
52+
# Check spec_guids match meta block
53+
spec_guid_attrs = get_attrs(product, ApigeeProductAttributeApiSpecGuid)
54+
if meta.api.spec_guids is None and len(spec_guid_attrs) != 0:
55+
msg = (
56+
f"product {product.name} has a spec_guid "
57+
+ "attribute when spec_guids are not defined "
58+
+ "in meta.api block"
59+
)
60+
raise AssertionError(msg)
2561
elif meta.api.spec_guids is not None:
2662
if len(spec_guid_attrs) != 1:
27-
raise AssertionError(
28-
f"product {product.name} requires unique attribute spec_guid"
63+
msg = (
64+
f"product {product.name} requires "
65+
+ "unique attribute spec_guid"
2966
)
67+
raise AssertionError(msg)
3068
elif spec_guid_attrs[0].value not in meta.api.spec_guids:
31-
raise AssertionError(f"product {product.name} attribute spec_guid must match one of meta.api.spec_guids= {[str(g) for g in meta.api.spec_guids]}, supplied value is {spec_guid_attrs[0].value}")
69+
msg = (
70+
f"product {product.name} attribute spec_guid must "
71+
+ "match one of meta.api.spec_guids= "
72+
+ f"{[str(g) for g in meta.api.spec_guids]}, "
73+
+ "supplied value is {spec_guid_attrs[0].value}"
74+
)
75+
raise AssertionError(msg)
76+
3277
return meta

ansible/collections/ansible_collections/nhsd/apigee/plugins/module_utils/models/manifest/meta.py

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,52 +8,52 @@
88
# be a proper microservice. But for now, this will do.
99
REGISTERED_META = [
1010
{
11-
"id": "96836235-09a5-4064-9220-0812765ebdd7",
11+
"guid": "96836235-09a5-4064-9220-0812765ebdd7",
1212
"name": "canary-api",
1313
"spec_guids": {"0af08cfb-6835-47b5-867c-95d41ef849b5", },
1414
},
1515
{
16-
"id": "b3d5c83f-98f2-429c-ba1d-646dccd139a3",
16+
"guid": "b3d5c83f-98f2-429c-ba1d-646dccd139a3",
1717
"name": "hello-world",
1818
"spec_guids": {"e8663c19-725f-4883-b272-a9da868d5541", },
1919
},
2020
# {
21-
# "id": "a3213966-9b13-400c-8fdd-e239e56a2742",
21+
# "guid": "a3213966-9b13-400c-8fdd-e239e56a2742",
2222
# "name": "mesh-api",
2323
# "spec_guids": {"73a3d6dd-912b-4793-bf4c-bf80c4bc34d1", },
2424
# },
2525
{
26-
"id": "eef32850-52ae-46da-9dbd-d9f3df818846",
26+
"guid": "eef32850-52ae-46da-9dbd-d9f3df818846",
2727
"name": "personal-demographics",
2828
"spec_guids": {"a343a204-f2d2-4287-a2e5-b5cb367e35bb", },
2929
},
3030
{
31-
"id": "9c644a26-c926-4fae-9564-5a9c49ab332d",
31+
"guid": "9c644a26-c926-4fae-9564-5a9c49ab332d",
3232
"name": "electronic-prescription-service-api",
3333
"spec_guids": {"5ead5713-9d2b-46eb-8626-def5fd2a2350", },
3434
},
3535
{
36-
"id": "13cfc3dd-38c3-4692-9cfb-50d540e8cfe3",
36+
"guid": "13cfc3dd-38c3-4692-9cfb-50d540e8cfe3",
3737
"name": "reasonable-adjustments",
3838
"spec_guids": {"9f2d5659-ef7d-4815-ac25-d11f4ce75c25", },
3939
},
4040
{
41-
"id": "090e12f4-6f3c-4ea7-b7eb-d70687d22cea",
41+
"guid": "090e12f4-6f3c-4ea7-b7eb-d70687d22cea",
4242
"name": "ambulance-analytics",
4343
"spec_guids": {"538699e8-d039-4473-9e35-e3b79eb92d1e", },
4444
},
4545
{
46-
"id": "fa1c780f-6fb6-4c8e-a73c-eb2c306ca4f1",
46+
"guid": "fa1c780f-6fb6-4c8e-a73c-eb2c306ca4f1",
4747
"name": "spine-directory-service",
4848
"spec_guids": {"88a3ec29-7ab1-4ac4-ae32-e367767b3ed8", },
4949
},
5050
{
51-
"id": "7541eb6b-3416-4aee-bd66-8766c1f90cfb",
51+
"guid": "7541eb6b-3416-4aee-bd66-8766c1f90cfb",
5252
"name": "nhs-app",
5353
"spec_guids": {"f5b9779e-d343-4a0a-8410-6dcae48bc55e", },
5454
},
5555
# {
56-
# "id": "b26b0249-488d-44f9-93ed-9d2f08f3859c",
56+
# "guid": "b26b0249-488d-44f9-93ed-9d2f08f3859c",
5757
# "name": "signing-service-api",
5858
# "spec_guids": {"a062e39c-b843-4833-8d24-8fc1434900a0",},
5959
# },
@@ -62,37 +62,52 @@
6262

6363
class ManifestMetaApi(pydantic.BaseModel):
6464
name: pydantic.constr(regex=r"^[a-z]+(-[a-z]+)*$")
65-
id: pydantic.UUID4
65+
id: typing.Optional[pydantic.UUID4] = pydantic.Field(
66+
None, description="This field is deprecated, use guid instead."
67+
)
68+
guid: pydantic.UUID4 = None
6669
spec_guids: typing.Optional[typing.Set[pydantic.UUID4]] = None
6770

6871
def dict(self, **kwargs):
6972
native = super().dict(**kwargs)
7073
spec_guids = native.get("spec_guids")
7174
if spec_guids is None:
7275
spec_guids = []
76+
if "id" in native: # Deprecated, do not export
77+
native.pop("id")
7378
native.update(
74-
{"id": str(native["id"]), "spec_guids": [str(_id) for _id in spec_guids]}
79+
{
80+
"guid": str(native["guid"]),
81+
"spec_guids": [str(guid) for guid in spec_guids],
82+
}
7583
)
7684
return native
7785

86+
@pydantic.validator("guid", pre=True, always=True)
87+
def id_to_guid(cls, guid, values):
88+
_id = values.get("id")
89+
if _id and not guid:
90+
return _id
91+
return guid
92+
7893
@pydantic.root_validator
7994
def validate_meta_api(cls, values):
80-
supplied_id = str(values.get("id"))
95+
supplied_guid = str(values.get("guid"))
8196

8297
try:
8398
registered_meta = next(
84-
filter(lambda meta: meta["id"] == supplied_id, REGISTERED_META)
99+
filter(lambda meta: meta["guid"] == supplied_guid, REGISTERED_META)
85100
)
86101
except StopIteration:
87102
raise ValueError(
88-
f"Supplied meta.api.id: '{supplied_id}' does not match any registered API id."
103+
f"Supplied meta.api.guid: '{supplied_guid}' does not match any registered API guid."
89104
)
90105

91106
registered_name = registered_meta["name"]
92107
supplied_name = values.get("name")
93108
if supplied_name != registered_name:
94109
raise ValueError(
95-
f"Supplied meta.api.name '{supplied_name}' does not match registered name {registered_name} for API id {supplied_id}."
110+
f"Supplied meta.api.name '{supplied_name}' does not match registered name {registered_name} for API guid {supplied_guid}."
96111
)
97112

98113
supplied_spec_guids = values.get("spec_guids")
@@ -103,7 +118,7 @@ def validate_meta_api(cls, values):
103118
for supplied_spec_guid in supplied_spec_guids:
104119
if str(supplied_spec_guid) not in registered_spec_guids:
105120
raise ValueError(
106-
f"Supplied meta.api.spec_guids entry '{supplied_spec_guid}' is not in list of registered spec_guids {registered_spec_guids} for API id '{supplied_id}'"
121+
f"Supplied meta.api.spec_guids entry '{supplied_spec_guid}' is not in list of registered spec_guids {registered_spec_guids} for API guid '{supplied_guid}'"
107122
)
108123

109124
return values

ansible/collections/ansible_collections/nhsd/apigee/tests/integration/targets/test_validate_manifest/files/canary-api-v1.1.0/manifest.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ apigee:
2323
value: public
2424
- name: ratelimit
2525
value: 5ps
26-
- name: spec_guid
26+
- name: api_spec_guid
2727
value: 0af08cfb-6835-47b5-867c-95d41ef849b5
28+
- name: api_guid
29+
value: 96836235-09a5-4064-9220-0812765ebdd7
2830
description: tweet tweet!
2931
displayName: Canary API (Internal Development)
3032
environments:
@@ -55,8 +57,10 @@ apigee:
5557
value: public
5658
- name: ratelimit
5759
value: 5ps
58-
- name: spec_guid
60+
- name: api_spec_guid
5961
value: 0af08cfb-6835-47b5-867c-95d41ef849b5
62+
- name: api_guid
63+
value: 96836235-09a5-4064-9220-0812765ebdd7
6064
description: tweet tweet
6165
displayName: Canary API (Internal QA)
6266
environments:

ansible/collections/ansible_collections/nhsd/apigee/tests/unit/plugins/module_utils/models/manifest/schema_versions/v1.1.0.json

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,35 @@
6262
"value"
6363
]
6464
},
65-
"ApigeeProductAttributeSpecGuid": {
66-
"title": "ApigeeProductAttributeSpecGuid",
65+
"ApigeeProductAttributeApiSpecGuid": {
66+
"title": "ApigeeProductAttributeApiSpecGuid",
67+
"description": "Convert a non-string value attribute to string on export.",
6768
"type": "object",
6869
"properties": {
6970
"name": {
7071
"title": "Name",
71-
"const": "spec_guid",
72+
"const": "api_spec_guid",
73+
"type": "string"
74+
},
75+
"value": {
76+
"title": "Value",
77+
"type": "string",
78+
"format": "uuid4"
79+
}
80+
},
81+
"required": [
82+
"name",
83+
"value"
84+
]
85+
},
86+
"ApigeeProductAttributeApiGuid": {
87+
"title": "ApigeeProductAttributeApiGuid",
88+
"description": "Convert a non-string value attribute to string on export.",
89+
"type": "object",
90+
"properties": {
91+
"name": {
92+
"title": "Name",
93+
"const": "api_guid",
7294
"type": "string"
7395
},
7496
"value": {
@@ -88,7 +110,7 @@
88110
"properties": {
89111
"name": {
90112
"title": "Name",
91-
"pattern": "^(?!(access|ratelimit|spec_guid)$)",
113+
"pattern": "^(?!(access|ratelimit|api_guid|api_spec_guid)$)",
92114
"type": "string"
93115
},
94116
"value": {
@@ -134,7 +156,10 @@
134156
"$ref": "#/definitions/ApigeeProductAttributeRateLimit"
135157
},
136158
{
137-
"$ref": "#/definitions/ApigeeProductAttributeSpecGuid"
159+
"$ref": "#/definitions/ApigeeProductAttributeApiSpecGuid"
160+
},
161+
{
162+
"$ref": "#/definitions/ApigeeProductAttributeApiGuid"
138163
},
139164
{
140165
"$ref": "#/definitions/ApigeeProductAttribute"
@@ -379,6 +404,12 @@
379404
},
380405
"id": {
381406
"title": "Id",
407+
"description": "This field is deprecated, use guid instead.",
408+
"type": "string",
409+
"format": "uuid4"
410+
},
411+
"guid": {
412+
"title": "Guid",
382413
"type": "string",
383414
"format": "uuid4"
384415
},
@@ -393,8 +424,7 @@
393424
}
394425
},
395426
"required": [
396-
"name",
397-
"id"
427+
"name"
398428
]
399429
},
400430
"ManifestMeta": {

0 commit comments

Comments
 (0)