ocmo-api 0.8.19__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. LICENSE +201 -0
  2. NOTICE +16 -0
  3. core/__init__.py +0 -0
  4. core/api/__init__.py +201 -0
  5. core/api/_common.py +37 -0
  6. core/api/audit.py +244 -0
  7. core/api/auth.py +50 -0
  8. core/api/config.py +96 -0
  9. core/api/errors.py +65 -0
  10. core/api/global_permissions.py +107 -0
  11. core/api/lock.py +99 -0
  12. core/api/namespace.py +86 -0
  13. core/api/propagation.py +29 -0
  14. core/api/resolve.py +229 -0
  15. core/api/resolver.py +96 -0
  16. core/api/secret.py +53 -0
  17. core/api/system.py +42 -0
  18. core/api/template.py +53 -0
  19. core/api/tree.py +274 -0
  20. core/apps.py +18 -0
  21. core/audit_operations.py +0 -0
  22. core/constants/__init__.py +5 -0
  23. core/constants/audit_operations.py +35 -0
  24. core/constants/permission_actions.py +59 -0
  25. core/constants/permissions.py +41 -0
  26. core/constants/resolve.py +3 -0
  27. core/constants/transformers.py +22 -0
  28. core/constants/webhook_events.py +31 -0
  29. core/data/builtin_schemas/_git_sync.schema.yaml +24 -0
  30. core/data/builtin_schemas/_permissions.schema.yaml +211 -0
  31. core/data/builtin_schemas/_webhooks.schema.yaml +145 -0
  32. core/decorators/__init__.py +14 -0
  33. core/decorators/audit.py +105 -0
  34. core/decorators/binding.py +49 -0
  35. core/decorators/permissions.py +129 -0
  36. core/decorators/webhook.py +87 -0
  37. core/exceptions.py +165 -0
  38. core/management/__init__.py +0 -0
  39. core/management/commands/__init__.py +0 -0
  40. core/management/commands/dump_openapi.py +40 -0
  41. core/managers/artifacts.py +346 -0
  42. core/managers/audit/__init__.py +1172 -0
  43. core/managers/audit/timeline.py +365 -0
  44. core/managers/auth.py +276 -0
  45. core/managers/cast.py +506 -0
  46. core/managers/config_validation.py +216 -0
  47. core/managers/crypto.py +78 -0
  48. core/managers/global_permissions.py +183 -0
  49. core/managers/health.py +56 -0
  50. core/managers/lock.py +229 -0
  51. core/managers/namespace.py +132 -0
  52. core/managers/permissions.py +168 -0
  53. core/managers/propagation.py +244 -0
  54. core/managers/resolution.py +938 -0
  55. core/managers/resolve_cache.py +266 -0
  56. core/managers/resolve_parameters.py +503 -0
  57. core/managers/resolver.py +56 -0
  58. core/managers/resolver_tokens.py +114 -0
  59. core/managers/resolving.py +673 -0
  60. core/managers/secret.py +147 -0
  61. core/managers/system.py +44 -0
  62. core/managers/tree/__init__.py +85 -0
  63. core/managers/tree/_common.py +85 -0
  64. core/managers/tree/base.py +203 -0
  65. core/managers/tree/config_ops.py +167 -0
  66. core/managers/tree/constants.py +11 -0
  67. core/managers/tree/create.py +75 -0
  68. core/managers/tree/diff.py +270 -0
  69. core/managers/tree/mutate.py +433 -0
  70. core/managers/tree/paths.py +49 -0
  71. core/managers/tree/propagation.py +200 -0
  72. core/managers/tree/read.py +263 -0
  73. core/managers/tree/resolver_ops.py +21 -0
  74. core/managers/tree/tags.py +167 -0
  75. core/managers/tree/update.py +137 -0
  76. core/managers/tree/versions.py +87 -0
  77. core/managers/tree_capabilities.py +189 -0
  78. core/managers/webhook/__init__.py +3 -0
  79. core/managers/webhook/cache.py +68 -0
  80. core/managers/webhook/main.py +357 -0
  81. core/middleware.py +32 -0
  82. core/migrations/0001_initial.py +157 -0
  83. core/migrations/0002_folder.py +33 -0
  84. core/migrations/0003_alter_namespace_name.py +17 -0
  85. core/migrations/0004_alter_namespace_name_and_more.py +25 -0
  86. core/migrations/0005_configversion_templateversion.py +85 -0
  87. core/migrations/0006_alter_templateversion_options_and_more.py +47 -0
  88. core/migrations/0007_configversion_deleted_at_templateversion_deleted_at.py +22 -0
  89. core/migrations/0008_namespace_tags_dek_secret_global_permissions.py +139 -0
  90. core/migrations/0009_tree_lock.py +58 -0
  91. core/migrations/0010_resolver_token_encryption.py +75 -0
  92. core/migrations/0011_audit_event.py +134 -0
  93. core/migrations/0012_audit_event_namespace_name.py +24 -0
  94. core/migrations/0013_audit_event_object_id.py +28 -0
  95. core/migrations/0014_sync_permissions_webhooks_builtin_schemas.py +26 -0
  96. core/migrations/0015_audit_event_operation_subresource.py +27 -0
  97. core/migrations/0016_sync_permissions_schema_audit_actions.py +26 -0
  98. core/migrations/0017_sync_permissions_schema_valid_actions.py +26 -0
  99. core/migrations/__init__.py +0 -0
  100. core/models/__init__.py +9 -0
  101. core/models/audit.py +97 -0
  102. core/models/config.py +132 -0
  103. core/models/folder.py +12 -0
  104. core/models/global_permissions.py +27 -0
  105. core/models/lock.py +35 -0
  106. core/models/namespace.py +45 -0
  107. core/models/resolver.py +25 -0
  108. core/models/secret.py +58 -0
  109. core/models/tree.py +29 -0
  110. core/notice.py +18 -0
  111. core/operation_ids.py +153 -0
  112. core/schemas/__init__.py +2 -0
  113. core/schemas/audit.py +75 -0
  114. core/schemas/cast_json_schema.py +57 -0
  115. core/schemas/cast_options.py +508 -0
  116. core/schemas/generic.py +233 -0
  117. core/schemas/metadata_json_schema.py +21 -0
  118. core/schemas/propagation.py +88 -0
  119. core/schemas/requests.py +773 -0
  120. core/schemas/resolver_json_schema.py +65 -0
  121. core/schemas/responses.py +690 -0
  122. core/schemas/webhooks.py +40 -0
  123. core/shortcuts.py +415 -0
  124. core/tests/__init__.py +0 -0
  125. core/tests/auth_helpers.py +23 -0
  126. core/tests/namespace_helpers.py +29 -0
  127. core/tests/test_api_cli.py +43 -0
  128. core/tests/test_api_errors.py +151 -0
  129. core/tests/test_artifacts.py +123 -0
  130. core/tests/test_audit.py +973 -0
  131. core/tests/test_audit_event_id.py +90 -0
  132. core/tests/test_audit_operations.py +222 -0
  133. core/tests/test_builtin_config_schemas.py +174 -0
  134. core/tests/test_can_i.py +510 -0
  135. core/tests/test_cast_format_options.py +57 -0
  136. core/tests/test_cast_options_schema.py +54 -0
  137. core/tests/test_cast_yaml.py +26 -0
  138. core/tests/test_config_schema_api.py +173 -0
  139. core/tests/test_config_validation.py +378 -0
  140. core/tests/test_document_parser.py +122 -0
  141. core/tests/test_dump_openapi.py +46 -0
  142. core/tests/test_extend_key.py +267 -0
  143. core/tests/test_folder_get_item.py +23 -0
  144. core/tests/test_folder_resolve_missing_tags.py +73 -0
  145. core/tests/test_get_item_version.py +31 -0
  146. core/tests/test_global_permission_create.py +171 -0
  147. core/tests/test_list_pagination.py +262 -0
  148. core/tests/test_locks.py +209 -0
  149. core/tests/test_multiline_transformer.py +132 -0
  150. core/tests/test_namespace_create.py +11 -0
  151. core/tests/test_ocmo_metadata.py +127 -0
  152. core/tests/test_oidc_jwt_audiences.py +64 -0
  153. core/tests/test_omit_transformer.py +78 -0
  154. core/tests/test_operation_ids.py +47 -0
  155. core/tests/test_permissions.py +1148 -0
  156. core/tests/test_propagation.py +441 -0
  157. core/tests/test_public_base_url.py +20 -0
  158. core/tests/test_resolve_draft.py +176 -0
  159. core/tests/test_resolve_parameters_version.py +181 -0
  160. core/tests/test_resolve_permissions.py +492 -0
  161. core/tests/test_resolve_resolver_response.py +107 -0
  162. core/tests/test_resolver_auth.py +33 -0
  163. core/tests/test_resolver_folder_filters.py +95 -0
  164. core/tests/test_resolver_get_item.py +23 -0
  165. core/tests/test_resolver_navigate_search_scope.py +98 -0
  166. core/tests/test_resolver_schema_api.py +91 -0
  167. core/tests/test_security_hardening.py +324 -0
  168. core/tests/test_selectors.py +65 -0
  169. core/tests/test_special_namespace_paths.py +409 -0
  170. core/tests/test_stable_promotion.py +124 -0
  171. core/tests/test_system.py +140 -0
  172. core/tests/test_tree_manager_for_item.py +192 -0
  173. core/tests/test_two_layer_cache.py +334 -0
  174. core/tests/test_url_trailing_slashes.py +53 -0
  175. core/tests/test_validation_error_responses.py +43 -0
  176. core/tests/test_webhooks.py +985 -0
  177. core/utils/__init__.py +23 -0
  178. core/utils/deep_merge.py +91 -0
  179. core/utils/namespace_special_configs.py +105 -0
  180. core/utils/permissions_compiler.py +627 -0
  181. core/utils/permissions_schema_document.py +52 -0
  182. core/validation_errors.py +105 -0
  183. core/version.py +27 -0
  184. core/views.py +1 -0
  185. ocmo_api-0.8.19.dist-info/METADATA +252 -0
  186. ocmo_api-0.8.19.dist-info/RECORD +203 -0
  187. ocmo_api-0.8.19.dist-info/WHEEL +4 -0
  188. ocmo_api-0.8.19.dist-info/entry_points.txt +2 -0
  189. ocmo_api-0.8.19.dist-info/licenses/LICENSE +201 -0
  190. ocmo_api-0.8.19.dist-info/licenses/NOTICE +16 -0
  191. ocmoapi/__init__.py +1 -0
  192. ocmoapi/__main__.py +6 -0
  193. ocmoapi/asgi.py +16 -0
  194. ocmoapi/auth.py +163 -0
  195. ocmoapi/cli.py +85 -0
  196. ocmoapi/oauth2_provider.py +23 -0
  197. ocmoapi/parser.py +120 -0
  198. ocmoapi/settings.py +275 -0
  199. ocmoapi/templates/ninja/oauth2-redirect.html +81 -0
  200. ocmoapi/templates/ninja/swagger.html +55 -0
  201. ocmoapi/testing_auth.py +64 -0
  202. ocmoapi/urls.py +40 -0
  203. ocmoapi/wsgi.py +16 -0
core/api/tree.py ADDED
@@ -0,0 +1,274 @@
1
+ from ninja import Query, Router
2
+ from ninja.pagination import paginate
3
+
4
+ from ..managers.audit import AuditManager
5
+ from ..managers.auth import AuthManager
6
+ from ..managers.namespace import NamespaceManager
7
+ from ..managers.tree import TreeManager
8
+ from ..operation_ids import (
9
+ COPY_ITEM,
10
+ DELETE_ITEM,
11
+ DESCRIBE_ITEM,
12
+ DIFF_ITEM,
13
+ GET_ITEM,
14
+ LIST_ITEM_VERSIONS,
15
+ MOVE_ITEM,
16
+ NAVIGATE_PATH,
17
+ NAVIGATE_ROOT,
18
+ SEARCH_PATH,
19
+ SEARCH_ROOT,
20
+ SET_TAG,
21
+ )
22
+ from ..schemas import (
23
+ AnyExtendedNodeSchema,
24
+ AnyNodeSchema,
25
+ CopiedItemsSchema,
26
+ DeleteSchema,
27
+ DescribePayload,
28
+ DiffResponseSchema,
29
+ ErrorSchema,
30
+ ExtendedConfigSchema,
31
+ InfoSchema,
32
+ LocationPayload,
33
+ NavigationSchema,
34
+ TagPayload,
35
+ TreeNavigationNodeSchema,
36
+ VersionHistoryResponseSchema,
37
+ )
38
+
39
+ router = Router()
40
+
41
+
42
+ @router.get(
43
+ "/ns/{namespace}/~navigate/",
44
+ response={200: NavigationSchema, 404: ErrorSchema},
45
+ tags=["Tree"],
46
+ operation_id=NAVIGATE_ROOT,
47
+ )
48
+ def navigate_root(
49
+ request,
50
+ namespace: str,
51
+ recursive: bool = False,
52
+ limit: int = Query(100, ge=1),
53
+ offset: int = Query(0, ge=0),
54
+ ):
55
+ """Navigate tree from root."""
56
+ auth = AuthManager.from_request(request)
57
+ ns = NamespaceManager(namespace, auth=auth).get_or_raise()
58
+ AuditManager.bind(request, auth, namespace=ns)
59
+ return TreeManager(ns, "", auth=auth).navigate(recursive=recursive, limit=limit, offset=offset)
60
+
61
+
62
+ @router.get(
63
+ "/ns/{namespace}/~navigate/{path:path}",
64
+ response={200: NavigationSchema, 404: ErrorSchema},
65
+ tags=["Tree"],
66
+ operation_id=NAVIGATE_PATH,
67
+ )
68
+ def navigate_path(
69
+ request,
70
+ namespace: str,
71
+ path: str,
72
+ recursive: bool = False,
73
+ limit: int = Query(100, ge=1),
74
+ offset: int = Query(0, ge=0),
75
+ ):
76
+ """Navigate tree at a specific path."""
77
+ auth = AuthManager.from_request(request)
78
+ ns = NamespaceManager(namespace, auth=auth).get_or_raise()
79
+ AuditManager.bind(request, auth, namespace=ns)
80
+ return TreeManager(ns, path, auth=auth).navigate(recursive=recursive, limit=limit, offset=offset)
81
+
82
+
83
+ @router.get(
84
+ "/ns/{namespace}/~search/",
85
+ response={200: list[TreeNavigationNodeSchema], 404: ErrorSchema},
86
+ tags=["Tree"],
87
+ operation_id=SEARCH_ROOT,
88
+ )
89
+ @paginate
90
+ def search_root(request, namespace: str, q: str | None = None, types: list[str] = Query(None)):
91
+ """Search from namespace root."""
92
+ auth = AuthManager.from_request(request)
93
+ ns = NamespaceManager(namespace, auth=auth).get_or_raise()
94
+ AuditManager.bind(request, auth, namespace=ns)
95
+ return TreeManager(ns, "", auth=auth).search(query=q, node_types=types)
96
+
97
+
98
+ @router.get(
99
+ "/ns/{namespace}/~search/{path:path}",
100
+ response={200: list[TreeNavigationNodeSchema], 404: ErrorSchema},
101
+ tags=["Tree"],
102
+ operation_id=SEARCH_PATH,
103
+ )
104
+ @paginate
105
+ def search_path(request, namespace: str, path: str, q: str | None = None, types: list[str] = Query(None)):
106
+ """Search within a subtree."""
107
+ auth = AuthManager.from_request(request)
108
+ ns = NamespaceManager(namespace, auth=auth).get_or_raise()
109
+ AuditManager.bind(request, auth, namespace=ns)
110
+ return TreeManager(ns, path, auth=auth).search(query=q, node_types=types)
111
+
112
+
113
+ @router.get(
114
+ "/ns/{namespace}/~versions/{path:path}",
115
+ response={200: VersionHistoryResponseSchema, 404: ErrorSchema, 422: ErrorSchema},
116
+ tags=["Tree"],
117
+ operation_id=LIST_ITEM_VERSIONS,
118
+ )
119
+ def list_item_versions(
120
+ request,
121
+ namespace: str,
122
+ path: str,
123
+ limit: int = Query(100, ge=1),
124
+ offset: int = Query(0, ge=0),
125
+ q: str | None = Query(None),
126
+ tagged_only: bool = Query(False),
127
+ ):
128
+ """List all versions (metadata) for a config, template, or secret."""
129
+ auth = AuthManager.from_request(request)
130
+ ns = NamespaceManager(namespace, auth=auth).get_or_raise()
131
+ AuditManager.bind(request, auth, namespace=ns)
132
+ return TreeManager(ns, path, auth=auth).list_versions(
133
+ limit=limit,
134
+ offset=offset,
135
+ q=q,
136
+ tagged_only=tagged_only,
137
+ )
138
+
139
+
140
+ @router.get(
141
+ "/ns/{namespace}/~get/{path:path}",
142
+ response={200: AnyExtendedNodeSchema, 404: ErrorSchema},
143
+ tags=["Tree"],
144
+ operation_id=GET_ITEM,
145
+ )
146
+ def get_item(
147
+ request,
148
+ namespace: str,
149
+ path: str,
150
+ version: str = Query("latest"),
151
+ reveal: bool = Query(False),
152
+ ):
153
+ """Get item details. For secrets, include ?reveal=true for decrypted content."""
154
+ auth = AuthManager.from_request(request)
155
+ ns = NamespaceManager(namespace, auth=auth).get_or_raise()
156
+ AuditManager.bind(request, auth, namespace=ns)
157
+ return TreeManager(ns, path, auth=auth).get_extended(version=version, reveal=reveal)
158
+
159
+
160
+ @router.get(
161
+ "/ns/{namespace}/~diff/{path:path}",
162
+ response={200: DiffResponseSchema, 404: ErrorSchema, 422: ErrorSchema},
163
+ tags=["Tree"],
164
+ operation_id=DIFF_ITEM,
165
+ )
166
+ def diff_item(
167
+ request,
168
+ namespace: str,
169
+ path: str,
170
+ from_ref: str = Query("latest", alias="from"),
171
+ to_ref: str = Query("latest", alias="to"),
172
+ to_path: str | None = Query(None),
173
+ reveal: bool = Query(False),
174
+ ):
175
+ """
176
+ Diff two versions of the same item (?from=, ?to=) or two items (?to_path=).
177
+
178
+ Returns both sides for client-side diff rendering. For secrets, use
179
+ ?reveal=true to include decrypted content; otherwise decryption_required is set.
180
+ """
181
+ auth = AuthManager.from_request(request)
182
+ ns = NamespaceManager(namespace, auth=auth).get_or_raise()
183
+ AuditManager.bind(request, auth, namespace=ns)
184
+ return TreeManager(ns, path, auth=auth).diff_item(
185
+ from_ref=from_ref,
186
+ to_ref=to_ref,
187
+ to_path=to_path,
188
+ reveal=reveal,
189
+ )
190
+
191
+
192
+ @router.delete(
193
+ "/ns/{namespace}/~delete/{path:path}",
194
+ response={200: DeleteSchema, 404: ErrorSchema},
195
+ tags=["Tree"],
196
+ operation_id=DELETE_ITEM,
197
+ )
198
+ def delete_item(
199
+ request,
200
+ namespace: str,
201
+ path: str,
202
+ preview: bool = Query(True),
203
+ version: str | None = Query(
204
+ None,
205
+ description="Soft-delete only this version (number or tag, e.g. 2 or stable)",
206
+ ),
207
+ ):
208
+ """Delete a tree item or a specific version. preview=true (default) is a dry run."""
209
+ auth = AuthManager.from_request(request)
210
+ ns = NamespaceManager(namespace, auth=auth).get_or_raise()
211
+ AuditManager.bind(request, auth, namespace=ns)
212
+ return TreeManager(ns, path, auth=auth).delete_item(preview, version)
213
+
214
+
215
+ @router.post(
216
+ "/ns/{namespace}/~move/{path:path}",
217
+ response={200: AnyNodeSchema, 404: ErrorSchema, 409: ErrorSchema},
218
+ tags=["Tree"],
219
+ operation_id=MOVE_ITEM,
220
+ )
221
+ def move_item(request, namespace: str, path: str, payload: LocationPayload):
222
+ """Move an item or folder subtree to a new path."""
223
+ auth = AuthManager.from_request(request)
224
+ ns = NamespaceManager(namespace, auth=auth).get_or_raise()
225
+ AuditManager.bind(request, auth, namespace=ns)
226
+ return TreeManager(ns, path, auth=auth).move_item(payload.target_path)
227
+
228
+
229
+ @router.post(
230
+ "/ns/{namespace}/~copy/{path:path}",
231
+ response={200: CopiedItemsSchema, 404: ErrorSchema, 409: ErrorSchema},
232
+ tags=["Tree"],
233
+ operation_id=COPY_ITEM,
234
+ )
235
+ def copy_item(
236
+ request,
237
+ namespace: str,
238
+ path: str,
239
+ payload: LocationPayload,
240
+ tag_to_copy: str = Query("latest"),
241
+ ):
242
+ """Copy an item or subtree. Only the version at tag_to_copy is copied."""
243
+ auth = AuthManager.from_request(request)
244
+ ns = NamespaceManager(namespace, auth=auth).get_or_raise()
245
+ AuditManager.bind(request, auth, namespace=ns)
246
+ return TreeManager(ns, path, auth=auth).copy_item(payload.target_path.strip("/"), tag_to_copy)
247
+
248
+
249
+ @router.post(
250
+ "/ns/{namespace}/~tag/{path:path}",
251
+ response={200: ExtendedConfigSchema, 204: InfoSchema, 404: ErrorSchema, 422: ErrorSchema},
252
+ tags=["Tree"],
253
+ operation_id=SET_TAG,
254
+ )
255
+ def set_tag(request, namespace: str, path: str, payload: TagPayload):
256
+ """Set or delete a tag on a Config, Template, or Secret."""
257
+ auth = AuthManager.from_request(request)
258
+ ns = NamespaceManager(namespace, auth=auth).get_or_raise()
259
+ AuditManager.bind(request, auth, namespace=ns)
260
+ return TreeManager(ns, path, auth=auth).set_item_tag(payload)
261
+
262
+
263
+ @router.post(
264
+ "/ns/{namespace}/~describe/{path:path}",
265
+ response={200: AnyNodeSchema, 404: ErrorSchema},
266
+ tags=["Tree"],
267
+ operation_id=DESCRIBE_ITEM,
268
+ )
269
+ def describe_item(request, namespace: str, path: str, payload: DescribePayload):
270
+ """Set the Markdown description of any tree item without creating a new version."""
271
+ auth = AuthManager.from_request(request)
272
+ ns = NamespaceManager(namespace, auth=auth).get_or_raise()
273
+ AuditManager.bind(request, auth, namespace=ns)
274
+ return TreeManager(ns, path, auth=auth).set_description(payload.description)
core/apps.py ADDED
@@ -0,0 +1,18 @@
1
+ import logging
2
+
3
+ from django.apps import AppConfig
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+
8
+ class CoreConfig(AppConfig):
9
+ default_auto_field = "django.db.models.BigAutoField"
10
+ name = "core"
11
+
12
+ def ready(self) -> None:
13
+ from .notice import load_notice_text
14
+
15
+ try:
16
+ logger.info("OCMO product notice:\n%s", load_notice_text().rstrip())
17
+ except FileNotFoundError as exc:
18
+ logger.warning("%s", exc)
File without changes
@@ -0,0 +1,5 @@
1
+ from .permissions import PROBE_OPERATIONS
2
+ from .resolve import OMIT
3
+ from .transformers import KNOWN_TRANSFORMERS, ParameterTransformer
4
+
5
+ __all__ = ["KNOWN_TRANSFORMERS", "OMIT", "ParameterTransformer", "PROBE_OPERATIONS"]
@@ -0,0 +1,35 @@
1
+ """Canonical user-friendly audit operation labels stored on ``AuditEvent.operation``."""
2
+
3
+ OP_RESOLVE = "Resolve"
4
+ OP_REFERENCED_IN_RESOLVE = "Referenced in resolve"
5
+ OP_UPDATE_ITEM = "Update item"
6
+ OP_CREATE_ITEM = "Create item"
7
+ OP_DELETE_ITEM = "Delete item"
8
+ OP_SET_TAG = "Set tag"
9
+ OP_DELETE_TAG = "Delete tag"
10
+ OP_DIFF_ITEM = "Diff item"
11
+ OP_LIST_VERSIONS = "List versions"
12
+ OP_NAVIGATE = "Navigate"
13
+ OP_SEARCH = "Search"
14
+ OP_READ_LOCK = "Read lock"
15
+ OP_CREATE_LOCK = "Create lock"
16
+ OP_UPDATE_LOCK = "Update lock"
17
+ OP_DELETE_LOCK = "Delete lock"
18
+ OP_LIST_LOCKS = "List locks"
19
+ OP_PROPAGATE_CONFIG = "Propagate config"
20
+ OP_ROTATE_TOKEN = "Rotate token"
21
+ OP_DOWNLOAD_ARTIFACT = "Download artifact"
22
+ OP_UPDATE_DESCRIPTION = "Update description"
23
+ OP_PROMOTE_STABLE_TAG = "Promote stable tag"
24
+ OP_COPY_ITEM = "Copy item"
25
+ OP_MOVE_ITEM = "Move item"
26
+ OP_READ_NAMESPACE = "Read namespace"
27
+ OP_CREATE_NAMESPACE = "Create namespace"
28
+ OP_UPDATE_NAMESPACE = "Update namespace"
29
+ OP_DELETE_NAMESPACE = "Delete namespace"
30
+ OP_LIST_PERMISSIONS = "List permissions"
31
+ OP_READ_PERMISSION = "Read permission"
32
+ OP_CREATE_PERMISSION = "Create permission"
33
+ OP_UPDATE_PERMISSION = "Update permission"
34
+ OP_DELETE_PERMISSION = "Delete permission"
35
+ OP_MOVE_PERMISSION = "Move permission"
@@ -0,0 +1,59 @@
1
+ """Permission policy action strings accepted in ``_permissions`` configs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ PERMISSION_ACTION_RESOURCES = ("config", "template", "resolver", "secret", "lock", "folder", "*")
6
+ PERMISSION_ACTION_VERBS = ("read", "write", "delete", "resolve", "tag", "describe", "audit", "*")
7
+
8
+ # Concrete ``<resource>:<verb>`` pairs enforced by namespace ABAC (tree managers / can-i probes).
9
+ PERMISSION_CONCRETE_ACTIONS: tuple[str, ...] = (
10
+ "config:read",
11
+ "config:write",
12
+ "config:delete",
13
+ "config:resolve",
14
+ "config:tag",
15
+ "config:describe",
16
+ "config:audit",
17
+ "template:read",
18
+ "template:write",
19
+ "template:delete",
20
+ "template:tag",
21
+ "template:describe",
22
+ "template:audit",
23
+ "resolver:read",
24
+ "resolver:write",
25
+ "resolver:delete",
26
+ "resolver:audit",
27
+ "secret:read",
28
+ "secret:write",
29
+ "secret:delete",
30
+ "secret:resolve",
31
+ "secret:tag",
32
+ "secret:describe",
33
+ "secret:audit",
34
+ "lock:read",
35
+ "lock:write",
36
+ "lock:delete",
37
+ "folder:describe",
38
+ "folder:audit",
39
+ )
40
+
41
+ PERMISSION_RESOURCE_WILDCARD_ACTIONS: tuple[str, ...] = tuple(
42
+ f"{resource}:*" for resource in ("config", "template", "resolver", "secret", "lock")
43
+ )
44
+
45
+ PERMISSION_VERB_WILDCARD_ACTIONS: tuple[str, ...] = tuple(
46
+ f"*:{verb}" for verb in ("read", "write", "delete", "resolve", "tag", "describe", "audit")
47
+ )
48
+
49
+ PERMISSION_ACTIONS: tuple[str, ...] = (
50
+ *PERMISSION_CONCRETE_ACTIONS,
51
+ *PERMISSION_RESOURCE_WILDCARD_ACTIONS,
52
+ *PERMISSION_VERB_WILDCARD_ACTIONS,
53
+ "*:*",
54
+ )
55
+
56
+ PERMISSION_ACTION_PATTERN = (
57
+ r"^(config|template|resolver|secret|lock|folder|\*):"
58
+ r"(read|write|delete|resolve|tag|describe|audit|\*)$"
59
+ )
@@ -0,0 +1,41 @@
1
+ """Operations accepted by POST /auth/can-i/ (concrete API checks, no wildcards)."""
2
+
3
+ PROBE_OPERATIONS = frozenset(
4
+ {
5
+ "global:admin",
6
+ "namespace:read",
7
+ "namespace:write",
8
+ "namespace:delete",
9
+ "namespace:audit",
10
+ "namespace:create",
11
+ "config:read",
12
+ "config:write",
13
+ "config:delete",
14
+ "config:resolve",
15
+ "config:tag",
16
+ "config:describe",
17
+ "config:audit",
18
+ "template:read",
19
+ "template:write",
20
+ "template:delete",
21
+ "template:tag",
22
+ "template:describe",
23
+ "template:audit",
24
+ "resolver:read",
25
+ "resolver:write",
26
+ "resolver:delete",
27
+ "resolver:audit",
28
+ "secret:read",
29
+ "secret:resolve",
30
+ "secret:write",
31
+ "secret:delete",
32
+ "secret:tag",
33
+ "secret:describe",
34
+ "secret:audit",
35
+ "lock:read",
36
+ "lock:write",
37
+ "lock:delete",
38
+ "folder:describe",
39
+ "folder:audit",
40
+ }
41
+ )
@@ -0,0 +1,3 @@
1
+ """Resolve pipeline sentinels shared across managers and utils."""
2
+
3
+ OMIT = object()
@@ -0,0 +1,22 @@
1
+ """Allowed parameter transformer names (validation only; runtime registry in resolve_parameters)."""
2
+
3
+ from typing import Literal, get_args
4
+
5
+ ParameterTransformer = Literal[
6
+ "lower",
7
+ "upper",
8
+ "slug",
9
+ "snake",
10
+ "trim",
11
+ "escape_html",
12
+ "b64_encode",
13
+ "urlencode",
14
+ "int",
15
+ "float",
16
+ "bool",
17
+ "null",
18
+ "multiline",
19
+ "omit",
20
+ ]
21
+
22
+ KNOWN_TRANSFORMERS = frozenset(get_args(ParameterTransformer))
@@ -0,0 +1,31 @@
1
+ """Webhook event names emitted by the API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ WEBHOOK_EVENTS: tuple[str, ...] = (
6
+ "config.created",
7
+ "config.updated",
8
+ "config.deleted",
9
+ "config.tagged",
10
+ "config.resolved",
11
+ "resolver.created",
12
+ "resolver.updated",
13
+ "secret.created",
14
+ "secret.updated",
15
+ "secret.deleted",
16
+ "secret.tagged",
17
+ "secret.resolved",
18
+ "namespace.updated",
19
+ "lock.created",
20
+ "lock.updated",
21
+ "lock.deleted",
22
+ "propagation.triggered",
23
+ )
24
+
25
+ WEBHOOK_PAYLOAD_PRESETS: tuple[str, ...] = (
26
+ "ocmo",
27
+ "generic_json",
28
+ "slack",
29
+ "teams",
30
+ "discord",
31
+ )
@@ -0,0 +1,24 @@
1
+ _ocmo:
2
+ is_json_schema: true
3
+ $schema: https://json-schema.org/draft/2020-12/schema
4
+ type: object
5
+ additionalProperties: false
6
+ required:
7
+ - enabled
8
+ properties:
9
+ enabled:
10
+ type: boolean
11
+ repository_url:
12
+ type: string
13
+ ssh_private_key:
14
+ type: string
15
+ git_username:
16
+ type: string
17
+ git_password:
18
+ type: string
19
+ branch:
20
+ type: string
21
+ path_prefix:
22
+ type: string
23
+ commit_versions:
24
+ type: boolean