granny-devops 0.15.0__tar.gz → 0.16.0__tar.gz

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 (110) hide show
  1. {granny_devops-0.15.0 → granny_devops-0.16.0}/PKG-INFO +13 -2
  2. {granny_devops-0.15.0 → granny_devops-0.16.0}/README.md +12 -1
  3. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/__init__.py +1 -1
  4. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/cli/zitadel.py +62 -0
  5. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/zitadel/client.py +47 -0
  6. {granny_devops-0.15.0 → granny_devops-0.16.0}/pyproject.toml +1 -1
  7. {granny_devops-0.15.0 → granny_devops-0.16.0}/.gitignore +0 -0
  8. {granny_devops-0.15.0 → granny_devops-0.16.0}/LICENSE +0 -0
  9. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/analyze/__init__.py +0 -0
  10. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/analyze/costs.py +0 -0
  11. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/analyze/credits.py +0 -0
  12. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/analyze/gpu_pricing.py +0 -0
  13. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/analyze/gpus.py +0 -0
  14. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/analyze/lambdas.py +0 -0
  15. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/analyze/vpcs.py +0 -0
  16. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/authentik/__init__.py +0 -0
  17. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/authentik/client.py +0 -0
  18. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/authentik/provision.py +0 -0
  19. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/aws/__init__.py +0 -0
  20. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/aws/bedrock.py +0 -0
  21. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/aws/quota.py +0 -0
  22. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/azure/__init__.py +0 -0
  23. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/azure/_client.py +0 -0
  24. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/azure/account.py +0 -0
  25. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/azure/deployment.py +0 -0
  26. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/azure/openai.py +0 -0
  27. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/azure/quota.py +0 -0
  28. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/azure/resourcegroup.py +0 -0
  29. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/azure/role.py +0 -0
  30. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/azure/sku.py +0 -0
  31. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/azure/vm.py +0 -0
  32. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/azure/webapp.py +0 -0
  33. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/cdn/__init__.py +0 -0
  34. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/cdn/bunny.py +0 -0
  35. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/cli/__init__.py +0 -0
  36. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/cli/analyze.py +0 -0
  37. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/cli/authentik.py +0 -0
  38. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/cli/aws.py +0 -0
  39. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/cli/azure.py +0 -0
  40. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/cli/cdn.py +0 -0
  41. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/cli/cloudflare.py +0 -0
  42. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/cli/create.py +0 -0
  43. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/cli/credentials.py +0 -0
  44. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/cli/dns.py +0 -0
  45. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/cli/docker.py +0 -0
  46. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/cli/edge.py +0 -0
  47. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/cli/elk.py +0 -0
  48. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/cli/email.py +0 -0
  49. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/cli/indexing.py +0 -0
  50. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/cli/main.py +0 -0
  51. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/cli/serverless.py +0 -0
  52. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/cli/storage.py +0 -0
  53. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/cloudflare/__init__.py +0 -0
  54. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/cloudflare/d1.py +0 -0
  55. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/cloudflare/r2.py +0 -0
  56. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/cloudflare/workers.py +0 -0
  57. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/create/__init__.py +0 -0
  58. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/create/auto_certificate.py +0 -0
  59. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/create/cloudfront-security-headers.js +0 -0
  60. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/create/manage-dns.sh +0 -0
  61. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/create/manage_mailjet_contacts.py +0 -0
  62. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/create/registrars.py +0 -0
  63. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/create/setup_aws_cloudfront.py +0 -0
  64. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/create/setup_bunny_edge_script.py +0 -0
  65. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/create/setup_bunny_storage.py +0 -0
  66. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/create/setup_cognito_identity_pool.py +0 -0
  67. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/create/setup_hetzner_bunny.py +0 -0
  68. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/create/setup_mailjet_dns.py +0 -0
  69. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/create/setup_private_cdn.py +0 -0
  70. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/create/setup_s3_website.py +0 -0
  71. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/create/setup_scaleway_container.py +0 -0
  72. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/create/setup_scaleway_faas.py +0 -0
  73. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/create/setup_workmail.py +0 -0
  74. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/create/www-redirect-function.js +0 -0
  75. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/credentials/__init__.py +0 -0
  76. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/credentials/secrets.py +0 -0
  77. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/dns/__init__.py +0 -0
  78. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/dns/base.py +0 -0
  79. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/dns/bunny.py +0 -0
  80. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/dns/cloudflare.py +0 -0
  81. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/dns/cloudns.py +0 -0
  82. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/dns/desec.py +0 -0
  83. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/dns/factory.py +0 -0
  84. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/dns/hetzner.py +0 -0
  85. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/dns/inwx.py +0 -0
  86. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/dns/manual.py +0 -0
  87. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/dns/records.py +0 -0
  88. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/docker/__init__.py +0 -0
  89. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/docker/build_base.py +0 -0
  90. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/edge/__init__.py +0 -0
  91. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/edge/bunny.py +0 -0
  92. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/elk/__init__.py +0 -0
  93. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/elk/client.py +0 -0
  94. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/email/__init__.py +0 -0
  95. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/email/mailjet.py +0 -0
  96. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/email/mailjet_contacts.py +0 -0
  97. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/email/ses_forwarding.py +0 -0
  98. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/email/stalwart.py +0 -0
  99. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/email/workmail.py +0 -0
  100. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/indexing/__init__.py +0 -0
  101. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/indexing/google.py +0 -0
  102. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/indexing/indexnow.py +0 -0
  103. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/report.py +0 -0
  104. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/serverless/__init__.py +0 -0
  105. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/serverless/scaleway.py +0 -0
  106. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/storage/__init__.py +0 -0
  107. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/storage/aws.py +0 -0
  108. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/storage/bunny.py +0 -0
  109. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/storage/hetzner.py +0 -0
  110. {granny_devops-0.15.0 → granny_devops-0.16.0}/granny/zitadel/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: granny-devops
3
- Version: 0.15.0
3
+ Version: 0.16.0
4
4
  Summary: Cloud tools collection -- AWS infrastructure, CDN, and DevOps automation
5
5
  Author-email: Martin Wieser <martin.wieser@pseekoo.com>
6
6
  License: MIT License
@@ -257,6 +257,10 @@ granny storage aws create my-website --website
257
257
  granny analyze vpcs --json-output
258
258
  granny analyze lambdas --region us-east-1 eu-west-1
259
259
 
260
+ # AWS Bedrock -- foundation models (read-only; on-demand access auto-enables on first invoke)
261
+ granny aws bedrock list-models --profile lularge --region eu-central-1 --by-provider Anthropic
262
+ granny aws bedrock check-access --model-id anthropic.claude-sonnet-4-20250514-v1:0 --profile lularge --region eu-central-1
263
+
260
264
  # Cross-cloud GPU / credit / cost inventory (AWS + GCP + Azure)
261
265
  granny analyze gpus # running GPUs everywhere
262
266
  granny analyze gpus --filter h100,h200 # find Hopper clusters
@@ -329,8 +333,15 @@ granny elk add-user user@example.com \
329
333
  granny azure account whoami # signed-in identity
330
334
  granny azure account list # subscriptions
331
335
  granny azure deployment list --subscription <id> --all-groups
336
+ granny azure group create --subscription <id> --name <rg> --location swedencentral
332
337
  granny azure openai accounts --subscription <id>
333
- granny azure openai deployments --subscription <id> --kind OpenAI
338
+ granny azure openai account create --subscription <id> --resource-group <rg> \
339
+ --name <acct> --location swedencentral # create an Azure OpenAI account
340
+ granny azure openai keys --subscription <id> --resource-group <rg> --account <acct>
341
+ granny azure openai deployments --subscription <id> --kind OpenAI --with-usage # + created_at & last-used (Azure Monitor)
342
+ granny azure openai deployment create --subscription <id> --resource-group <rg> \
343
+ --account <acct> --name gpt-5.4 --model gpt-5.4 --model-version 2026-03-05 \
344
+ --sku GlobalStandard --capacity 50 # create a model deployment
334
345
  granny azure webapp list --subscription <id>
335
346
  granny azure vm-sizes list --subscription <id> --location polandcentral
336
347
  granny azure vm list --subscription <id> # running VMs + power state
@@ -124,6 +124,10 @@ granny storage aws create my-website --website
124
124
  granny analyze vpcs --json-output
125
125
  granny analyze lambdas --region us-east-1 eu-west-1
126
126
 
127
+ # AWS Bedrock -- foundation models (read-only; on-demand access auto-enables on first invoke)
128
+ granny aws bedrock list-models --profile lularge --region eu-central-1 --by-provider Anthropic
129
+ granny aws bedrock check-access --model-id anthropic.claude-sonnet-4-20250514-v1:0 --profile lularge --region eu-central-1
130
+
127
131
  # Cross-cloud GPU / credit / cost inventory (AWS + GCP + Azure)
128
132
  granny analyze gpus # running GPUs everywhere
129
133
  granny analyze gpus --filter h100,h200 # find Hopper clusters
@@ -196,8 +200,15 @@ granny elk add-user user@example.com \
196
200
  granny azure account whoami # signed-in identity
197
201
  granny azure account list # subscriptions
198
202
  granny azure deployment list --subscription <id> --all-groups
203
+ granny azure group create --subscription <id> --name <rg> --location swedencentral
199
204
  granny azure openai accounts --subscription <id>
200
- granny azure openai deployments --subscription <id> --kind OpenAI
205
+ granny azure openai account create --subscription <id> --resource-group <rg> \
206
+ --name <acct> --location swedencentral # create an Azure OpenAI account
207
+ granny azure openai keys --subscription <id> --resource-group <rg> --account <acct>
208
+ granny azure openai deployments --subscription <id> --kind OpenAI --with-usage # + created_at & last-used (Azure Monitor)
209
+ granny azure openai deployment create --subscription <id> --resource-group <rg> \
210
+ --account <acct> --name gpt-5.4 --model gpt-5.4 --model-version 2026-03-05 \
211
+ --sku GlobalStandard --capacity 50 # create a model deployment
201
212
  granny azure webapp list --subscription <id>
202
213
  granny azure vm-sizes list --subscription <id> --location polandcentral
203
214
  granny azure vm list --subscription <id> # running VMs + power state
@@ -1,6 +1,6 @@
1
1
  """Granny -- Cloud tools collection for AWS infrastructure and DevOps automation."""
2
2
 
3
- __version__ = "0.15.0"
3
+ __version__ = "0.16.0"
4
4
  __all__ = [
5
5
  "get_secret",
6
6
  "load_secrets_into_env",
@@ -128,6 +128,68 @@ def create_oidc_app_cmd(
128
128
  )
129
129
 
130
130
 
131
+ @zitadel.command("set-redirect-uris")
132
+ @click.option("--project", required=True, help="Project name or id owning the app.")
133
+ @click.option("--app", "app_name", required=True, help="OIDC app name.")
134
+ @click.option(
135
+ "--redirect-uri",
136
+ "redirect_uris",
137
+ multiple=True,
138
+ required=True,
139
+ help="Allowed OIDC redirect URI (repeatable).",
140
+ )
141
+ @click.option(
142
+ "--post-logout-uri",
143
+ "post_logout_uris",
144
+ multiple=True,
145
+ help="Post-logout redirect URI to add (repeatable).",
146
+ )
147
+ @click.option(
148
+ "--replace",
149
+ is_flag=True,
150
+ help="Replace the redirect-URI set instead of merging with the existing one.",
151
+ )
152
+ @click.option("--dry-run", is_flag=True)
153
+ def set_redirect_uris_cmd(
154
+ project: str,
155
+ app_name: str,
156
+ redirect_uris: tuple[str, ...],
157
+ post_logout_uris: tuple[str, ...],
158
+ replace: bool,
159
+ dry_run: bool,
160
+ ) -> None:
161
+ """Set an existing OIDC app's allowed redirect URIs (idempotent).
162
+
163
+ Merges with the app's current redirect URIs by default (union, de-duped),
164
+ so re-running with the same set is a no-op. Use ``--replace`` to set exactly
165
+ the URIs given.
166
+ """
167
+ client = _client()
168
+ found = client.find_project(project)
169
+ project_id = found["id"] if found else project
170
+ app = client.find_oidc_app(project_id, app_name)
171
+ if not app:
172
+ raise click.ClickException(
173
+ f"OIDC app {app_name!r} not found in project {project!r}"
174
+ )
175
+ app_id = app.get("id")
176
+ if dry_run:
177
+ verb = "replace with" if replace else "merge in"
178
+ click.echo(
179
+ f"[DRY RUN] Would {verb} redirect URIs on app {app_name!r} "
180
+ f"({app_id}): {list(redirect_uris)}"
181
+ )
182
+ return
183
+ result = client.set_oidc_app_redirects(
184
+ project_id,
185
+ app_id, # type: ignore[arg-type]
186
+ list(redirect_uris),
187
+ replace=replace,
188
+ post_logout_uris=list(post_logout_uris),
189
+ )
190
+ click.echo(json.dumps(result, indent=2))
191
+
192
+
131
193
  @zitadel.command("enforce-mfa")
132
194
  @click.option(
133
195
  "--no-passkey",
@@ -262,6 +262,53 @@ class ZitadelClient:
262
262
  "POST", f"/management/v1/projects/{project_id}/apps/oidc", body=body
263
263
  )
264
264
 
265
+ def set_oidc_app_redirects(
266
+ self,
267
+ project_id: str,
268
+ app_id: str,
269
+ redirect_uris: list[str],
270
+ *,
271
+ replace: bool = False,
272
+ post_logout_uris: list[str] | None = None,
273
+ ) -> dict[str, Any]:
274
+ """Set an existing OIDC app's redirect URIs (idempotent).
275
+
276
+ Reads the app's current ``oidcConfig`` and PUTs it back with the merged
277
+ redirect URIs (Zitadel's update endpoint replaces the whole config, so
278
+ every field is round-tripped). With ``replace=False`` (default) the new
279
+ URIs are unioned with the existing set — order-stable, de-duplicated —
280
+ so re-running with the same URIs is a no-op. ``replace=True`` sets
281
+ exactly ``redirect_uris``.
282
+ """
283
+ oidc = dict(self.get_app(project_id, app_id).get("oidcConfig") or {})
284
+ existing = list(oidc.get("redirectUris") or [])
285
+ merged = (
286
+ list(dict.fromkeys(redirect_uris))
287
+ if replace
288
+ else list(dict.fromkeys([*existing, *redirect_uris]))
289
+ )
290
+ merged_logout = list(
291
+ dict.fromkeys([*(oidc.get("postLogoutRedirectUris") or []), *(post_logout_uris or [])])
292
+ )
293
+ body = {
294
+ "redirectUris": merged,
295
+ "responseTypes": oidc.get("responseTypes") or ["OIDC_RESPONSE_TYPE_CODE"],
296
+ "grantTypes": oidc.get("grantTypes")
297
+ or ["OIDC_GRANT_TYPE_AUTHORIZATION_CODE", "OIDC_GRANT_TYPE_REFRESH_TOKEN"],
298
+ "appType": oidc.get("appType") or "OIDC_APP_TYPE_WEB",
299
+ "authMethodType": oidc.get("authMethodType") or "OIDC_AUTH_METHOD_TYPE_BASIC",
300
+ "postLogoutRedirectUris": merged_logout,
301
+ "version": oidc.get("version") or "OIDC_VERSION_1_0",
302
+ "devMode": oidc.get("devMode", False),
303
+ "accessTokenType": oidc.get("accessTokenType") or "OIDC_TOKEN_TYPE_JWT",
304
+ }
305
+ self.request(
306
+ "PUT",
307
+ f"/management/v1/projects/{project_id}/apps/{app_id}/oidc_config",
308
+ body=body,
309
+ )
310
+ return {"appId": app_id, "redirectUris": merged}
311
+
265
312
  # ── Login policy / MFA ───────────────────────────────────────────────
266
313
 
267
314
  def get_login_policy(self) -> dict[str, Any]:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "granny-devops"
3
- version = "0.15.0"
3
+ version = "0.16.0"
4
4
  description = "Cloud tools collection -- AWS infrastructure, CDN, and DevOps automation"
5
5
  readme = "README.md"
6
6
  license = { file = "LICENSE" }
File without changes