granny-devops 0.4.0__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 (68) hide show
  1. granny/__init__.py +19 -0
  2. granny/analyze/__init__.py +6 -0
  3. granny/analyze/lambdas.py +59 -0
  4. granny/analyze/vpcs.py +57 -0
  5. granny/cdn/__init__.py +9 -0
  6. granny/cdn/bunny.py +231 -0
  7. granny/cli/__init__.py +0 -0
  8. granny/cli/analyze.py +66 -0
  9. granny/cli/cdn.py +210 -0
  10. granny/cli/create.py +94 -0
  11. granny/cli/credentials.py +99 -0
  12. granny/cli/dns.py +290 -0
  13. granny/cli/docker.py +165 -0
  14. granny/cli/edge.py +106 -0
  15. granny/cli/email.py +224 -0
  16. granny/cli/main.py +98 -0
  17. granny/cli/serverless.py +278 -0
  18. granny/cli/storage.py +249 -0
  19. granny/create/__init__.py +4 -0
  20. granny/create/auto_certificate.py +1899 -0
  21. granny/create/cloudfront-security-headers.js +53 -0
  22. granny/create/manage-dns.sh +321 -0
  23. granny/create/manage_mailjet_contacts.py +619 -0
  24. granny/create/registrars.py +363 -0
  25. granny/create/setup_aws_cloudfront.py +2808 -0
  26. granny/create/setup_bunny_edge_script.py +923 -0
  27. granny/create/setup_bunny_storage.py +1719 -0
  28. granny/create/setup_cognito_identity_pool.py +740 -0
  29. granny/create/setup_hetzner_bunny.py +1482 -0
  30. granny/create/setup_mailjet_dns.py +1103 -0
  31. granny/create/setup_private_cdn.py +547 -0
  32. granny/create/setup_s3_website.py +1512 -0
  33. granny/create/setup_scaleway_faas.py +1165 -0
  34. granny/create/setup_workmail.py +1217 -0
  35. granny/create/www-redirect-function.js +17 -0
  36. granny/credentials/__init__.py +15 -0
  37. granny/credentials/secrets.py +403 -0
  38. granny/dns/__init__.py +22 -0
  39. granny/dns/base.py +113 -0
  40. granny/dns/bunny.py +150 -0
  41. granny/dns/cloudflare.py +192 -0
  42. granny/dns/cloudns.py +162 -0
  43. granny/dns/desec.py +152 -0
  44. granny/dns/factory.py +72 -0
  45. granny/dns/hetzner.py +165 -0
  46. granny/dns/manual.py +64 -0
  47. granny/dns/records.py +29 -0
  48. granny/docker/__init__.py +5 -0
  49. granny/docker/build_base.py +204 -0
  50. granny/edge/__init__.py +5 -0
  51. granny/edge/bunny.py +147 -0
  52. granny/email/__init__.py +7 -0
  53. granny/email/mailjet.py +119 -0
  54. granny/email/mailjet_contacts.py +115 -0
  55. granny/email/ses_forwarding.py +281 -0
  56. granny/email/workmail.py +145 -0
  57. granny/report.py +128 -0
  58. granny/serverless/__init__.py +5 -0
  59. granny/serverless/scaleway.py +264 -0
  60. granny/storage/__init__.py +7 -0
  61. granny/storage/aws.py +113 -0
  62. granny/storage/bunny.py +98 -0
  63. granny/storage/hetzner.py +118 -0
  64. granny_devops-0.4.0.dist-info/METADATA +445 -0
  65. granny_devops-0.4.0.dist-info/RECORD +68 -0
  66. granny_devops-0.4.0.dist-info/WHEEL +4 -0
  67. granny_devops-0.4.0.dist-info/entry_points.txt +2 -0
  68. granny_devops-0.4.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,118 @@
1
+ """Hetzner Object Storage (S3-compatible) client.
2
+
3
+ Lifted from ``tools/create/setup_hetzner_bunny.py:HetznerS3Client``.
4
+ Uses boto3 with S3v4 signatures against Hetzner's S3-compatible endpoint.
5
+ """
6
+
7
+ import json
8
+ import logging
9
+ import time
10
+
11
+ import boto3
12
+ from botocore.config import Config
13
+
14
+ from granny.credentials.secrets import get_secret
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ HETZNER_S3_REGIONS = {
19
+ "fsn1": "fsn1.your-objectstorage.com",
20
+ "nbg1": "nbg1.your-objectstorage.com",
21
+ "hel1": "hel1.your-objectstorage.com",
22
+ }
23
+
24
+
25
+ class HetznerS3Client:
26
+ """Hetzner Object Storage bucket management (S3-compatible)."""
27
+
28
+ def __init__(self, region: str = "fsn1") -> None:
29
+ if region not in HETZNER_S3_REGIONS:
30
+ raise ValueError(
31
+ f"Unknown Hetzner region: {region}. "
32
+ f"Choices: {', '.join(HETZNER_S3_REGIONS)}"
33
+ )
34
+ self.region = region
35
+ self.endpoint = f"https://{HETZNER_S3_REGIONS[region]}"
36
+
37
+ access_key = get_secret("HETZNER_S3_ACCESS_KEY")
38
+ secret_key = get_secret("HETZNER_S3_SECRET_KEY")
39
+ if not access_key or not secret_key:
40
+ raise ValueError(
41
+ "HETZNER_S3_ACCESS_KEY / HETZNER_S3_SECRET_KEY not set (env or vault)."
42
+ )
43
+
44
+ self.client = boto3.client(
45
+ "s3",
46
+ endpoint_url=self.endpoint,
47
+ aws_access_key_id=access_key,
48
+ aws_secret_access_key=secret_key,
49
+ region_name=region,
50
+ config=Config(signature_version="s3v4"),
51
+ )
52
+
53
+ def bucket_exists(self, bucket_name: str) -> bool:
54
+ try:
55
+ self.client.head_bucket(Bucket=bucket_name)
56
+ return True
57
+ except Exception:
58
+ return False
59
+
60
+ def create_bucket(self, bucket_name: str) -> None:
61
+ if self.bucket_exists(bucket_name):
62
+ logger.info("Bucket %s already exists", bucket_name)
63
+ return
64
+ self.client.create_bucket(Bucket=bucket_name)
65
+ logger.info("Created bucket %s in %s", bucket_name, self.region)
66
+ time.sleep(3) # Hetzner eventual consistency
67
+
68
+ def set_bucket_policy_public(self, bucket_name: str) -> None:
69
+ policy = {
70
+ "Version": "2012-10-17",
71
+ "Statement": [
72
+ {
73
+ "Sid": "PublicReadGetObject",
74
+ "Effect": "Allow",
75
+ "Principal": "*",
76
+ "Action": "s3:GetObject",
77
+ "Resource": f"arn:aws:s3:::{bucket_name}/*",
78
+ }
79
+ ],
80
+ }
81
+ self.client.put_bucket_policy(Bucket=bucket_name, Policy=json.dumps(policy))
82
+ logger.info("Set public-read policy on %s", bucket_name)
83
+
84
+ def set_cors_config(
85
+ self, bucket_name: str, origins: list[str] | None = None
86
+ ) -> None:
87
+ cors = {
88
+ "CORSRules": [
89
+ {
90
+ "AllowedHeaders": ["*"],
91
+ "AllowedMethods": ["GET", "HEAD"],
92
+ "AllowedOrigins": origins or ["*"],
93
+ "MaxAgeSeconds": 3600,
94
+ }
95
+ ]
96
+ }
97
+ self.client.put_bucket_cors(Bucket=bucket_name, CORSConfiguration=cors)
98
+ logger.info("CORS configured on %s", bucket_name)
99
+
100
+ def upload_file(
101
+ self,
102
+ bucket_name: str,
103
+ key: str,
104
+ content: str | bytes,
105
+ content_type: str = "text/html",
106
+ cache_control: str = "max-age=300",
107
+ ) -> None:
108
+ body = content.encode("utf-8") if isinstance(content, str) else content
109
+ self.client.put_object(
110
+ Bucket=bucket_name,
111
+ Key=key,
112
+ Body=body,
113
+ ContentType=content_type,
114
+ CacheControl=cache_control,
115
+ )
116
+
117
+ def get_bucket_url(self, bucket_name: str) -> str:
118
+ return f"https://{bucket_name}.{HETZNER_S3_REGIONS[self.region]}"
@@ -0,0 +1,445 @@
1
+ Metadata-Version: 2.4
2
+ Name: granny-devops
3
+ Version: 0.4.0
4
+ Summary: Cloud tools collection -- AWS infrastructure, CDN, and DevOps automation
5
+ Author-email: Martin Wieser <martin.wieser@pseekoo.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Martin Wieser
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+ License-File: LICENSE
28
+ Requires-Python: >=3.13
29
+ Requires-Dist: boto3>=1.38
30
+ Requires-Dist: click>=8.1
31
+ Requires-Dist: python-dotenv>=1.1.0
32
+ Requires-Dist: requests>=2.32
33
+ Provides-Extra: all
34
+ Requires-Dist: anthropic>=0.54.0; extra == 'all'
35
+ Requires-Dist: azure-identity>=1.23.0; extra == 'all'
36
+ Requires-Dist: beautifulsoup4~=4.13.4; extra == 'all'
37
+ Requires-Dist: boto3-stubs>=1.40; extra == 'all'
38
+ Requires-Dist: certifi>=2025.6.15; extra == 'all'
39
+ Requires-Dist: cloudflare==4.3.1; extra == 'all'
40
+ Requires-Dist: earthengine-api>=1.5.20; extra == 'all'
41
+ Requires-Dist: elasticsearch-dsl<9.0.0,>=8.0.0; extra == 'all'
42
+ Requires-Dist: elasticsearch<9.0.0,>=8.0.0; extra == 'all'
43
+ Requires-Dist: flask-cors>=6.0.1; extra == 'all'
44
+ Requires-Dist: flask>=3.1.1; extra == 'all'
45
+ Requires-Dist: google-auth>=2.40.3; extra == 'all'
46
+ Requires-Dist: google-generativeai>=0.8.5; extra == 'all'
47
+ Requires-Dist: gspread>=6.2.1; extra == 'all'
48
+ Requires-Dist: hetzner-dns-api>=1.0.0; extra == 'all'
49
+ Requires-Dist: ipython~=9.3.0; extra == 'all'
50
+ Requires-Dist: json5>=0.12.0; extra == 'all'
51
+ Requires-Dist: jsonschema>=4.0.0; extra == 'all'
52
+ Requires-Dist: langchain-openai>=0.3.24; extra == 'all'
53
+ Requires-Dist: langchain>=0.3.26; extra == 'all'
54
+ Requires-Dist: langdetect>=1.0.9; extra == 'all'
55
+ Requires-Dist: litellm>=1.73.0; extra == 'all'
56
+ Requires-Dist: matplotlib>=3.10.3; extra == 'all'
57
+ Requires-Dist: msoffcrypto-tool>=5.4.2; extra == 'all'
58
+ Requires-Dist: numpy>=2.3.1; extra == 'all'
59
+ Requires-Dist: ollama>=0.5.1; extra == 'all'
60
+ Requires-Dist: openai>=1.90.0; extra == 'all'
61
+ Requires-Dist: openpyxl==3.1.5; extra == 'all'
62
+ Requires-Dist: pandas>=2.3.0; extra == 'all'
63
+ Requires-Dist: pdf2image>=1.17.0; extra == 'all'
64
+ Requires-Dist: pillow>=11.2.1; extra == 'all'
65
+ Requires-Dist: playwright>=1.52.0; extra == 'all'
66
+ Requires-Dist: pyairtable~=3.1.1; extra == 'all'
67
+ Requires-Dist: pydantic>=2.11.7; extra == 'all'
68
+ Requires-Dist: pymilvus>=2.5.11; extra == 'all'
69
+ Requires-Dist: pymongo>=4.13; extra == 'all'
70
+ Requires-Dist: pypdf>=5.6.1; extra == 'all'
71
+ Requires-Dist: pytesseract>=0.3.13; extra == 'all'
72
+ Requires-Dist: pyzbar>=0.1.9; extra == 'all'
73
+ Requires-Dist: selenium>=4.33.0; extra == 'all'
74
+ Requires-Dist: sentinelhub>=3.11.1; extra == 'all'
75
+ Requires-Dist: sentry-sdk>=2.30.0; extra == 'all'
76
+ Requires-Dist: tiktoken>=0.9.0; extra == 'all'
77
+ Requires-Dist: webdriver-manager>=4.0.2; extra == 'all'
78
+ Requires-Dist: zxing>=1.0.3; extra == 'all'
79
+ Provides-Extra: browser
80
+ Requires-Dist: playwright>=1.52.0; extra == 'browser'
81
+ Requires-Dist: selenium>=4.33.0; extra == 'browser'
82
+ Requires-Dist: webdriver-manager>=4.0.2; extra == 'browser'
83
+ Provides-Extra: cdn
84
+ Requires-Dist: cloudflare==4.3.1; extra == 'cdn'
85
+ Requires-Dist: hetzner-dns-api>=1.0.0; extra == 'cdn'
86
+ Provides-Extra: data
87
+ Requires-Dist: beautifulsoup4~=4.13.4; extra == 'data'
88
+ Requires-Dist: gspread>=6.2.1; extra == 'data'
89
+ Requires-Dist: openpyxl==3.1.5; extra == 'data'
90
+ Requires-Dist: pandas>=2.3.0; extra == 'data'
91
+ Requires-Dist: pyairtable~=3.1.1; extra == 'data'
92
+ Requires-Dist: pymongo>=4.13; extra == 'data'
93
+ Provides-Extra: dev
94
+ Requires-Dist: hatch>=1.14; extra == 'dev'
95
+ Requires-Dist: prek>=0.2; extra == 'dev'
96
+ Requires-Dist: pytest>=8.3; extra == 'dev'
97
+ Requires-Dist: ruff>=0.11; extra == 'dev'
98
+ Requires-Dist: twine>=6.1; extra == 'dev'
99
+ Provides-Extra: ml
100
+ Requires-Dist: anthropic>=0.54.0; extra == 'ml'
101
+ Requires-Dist: google-generativeai>=0.8.5; extra == 'ml'
102
+ Requires-Dist: langchain-openai>=0.3.24; extra == 'ml'
103
+ Requires-Dist: langchain>=0.3.26; extra == 'ml'
104
+ Requires-Dist: litellm>=1.73.0; extra == 'ml'
105
+ Requires-Dist: ollama>=0.5.1; extra == 'ml'
106
+ Requires-Dist: openai>=1.90.0; extra == 'ml'
107
+ Requires-Dist: tiktoken>=0.9.0; extra == 'ml'
108
+ Provides-Extra: vault
109
+ Description-Content-Type: text/markdown
110
+
111
+ # Granny
112
+
113
+ **Granny** is a comprehensive Cloud DevOps toolkit designed to simplify and automate infrastructure management across multiple cloud providers. Built for developers and operations teams, Granny provides a unified command-line interface for managing AWS resources, CDN configurations, DNS records, storage solutions, and more.
114
+
115
+ ## What is Granny?
116
+
117
+ Granny is your **Cloud Companion** — a collection of battle-tested tools that:
118
+
119
+ - **Analyzes** AWS resources (VPCs, Lambda functions) across all regions
120
+ - **Manages** Bunny CDN, including pull zones, cache purging, SSL certificates, and edge rules
121
+ - **Controls** DNS records across multiple providers (Cloudflare, Bunny, Hetzner, deSEC, ClouDNS)
122
+ - **Provisions** cloud infrastructure with reusable setup scripts
123
+ - **Builds** multi-architecture Docker images with deterministic tagging
124
+ - **Deploys** serverless functions to Scaleway FaaS
125
+ - **Handles** object storage across AWS S3, Bunny Storage, and Hetzner S3
126
+ - **Manages** email services (Mailjet sender setup, contact management, WorkMail users)
127
+ - **Supports** Bunny Edge Scripting for advanced CDN customization
128
+
129
+ Whether you're managing a single project or orchestrating infrastructure across multiple cloud providers, Granny provides the tools you need in one cohesive package.
130
+
131
+ ## Features
132
+
133
+ | Category | Capabilities |
134
+ |----------|--------------|
135
+ | **Analysis** | List VPCs and Lambda functions across AWS regions |
136
+ | **CDN** | Bunny CDN pull zone CRUD, cache purging, SSL certificates, edge rules |
137
+ | **DNS** | Multi-provider DNS management (Cloudflare, Bunny, Hetzner, deSEC, ClouDNS) |
138
+ | **Storage** | AWS S3, Bunny Storage, Hetzner S3 bucket and file management |
139
+ | **Serverless** | Scaleway Functions (FaaS) deployment and management |
140
+ | **Docker** | Multi-arch image builds with deterministic hash-based tagging |
141
+ | **Email** | Mailjet DNS setup, contact management, AWS WorkMail user administration |
142
+ | **Edge** | Bunny Edge Script creation, deployment, and environment management |
143
+ | **Credentials** | Secure secret management with environment variable resolution |
144
+ | **Infrastructure** | Reusable setup scripts for common cloud patterns |
145
+
146
+ ## Quick Start
147
+
148
+ ### Installation
149
+
150
+ ```shell
151
+ # Clone the repository
152
+ git clone https://github.com/your-org/granny-public.git
153
+ cd granny-public
154
+
155
+ # Install dependencies with uv
156
+ uv sync
157
+
158
+ # Or install globally from PyPI
159
+ pip install granny-devops
160
+ ```
161
+
162
+ Tagged releases are published to both pypi.org and the public GitLab PyPI registry. If PyPI has not propagated a fresh release yet, use the GitLab registry fallback:
163
+
164
+ ```shell
165
+ pip install --extra-index-url https://gitlab.com/api/v4/projects/81189862/packages/pypi/simple granny-devops
166
+ ```
167
+
168
+ ### Configuration
169
+
170
+ Granny reads secrets from environment variables. Copy the example file and fill in the values:
171
+
172
+ ```shell
173
+ cp .env.example .env
174
+ ```
175
+
176
+ Edit `.env` with your API keys and credentials. The following environment variables are supported:
177
+
178
+ - **Bunny**: `BUNNY_API_KEY` (plus per-customer `BUNNY_API_KEY_<SLUG>`)
179
+ - **Cloudflare**: `CLOUDFLARE_API_TOKEN`
180
+ - **deSEC**: `DESEC_API_TOKEN`
181
+ - **Hetzner DNS**: `HETZNER_DNS_API_TOKEN`
182
+ - **Hetzner S3**: `HETZNER_S3_ACCESS_KEY`, `HETZNER_S3_SECRET_KEY`
183
+ - **Scaleway**: `SCW_ACCESS_KEY`, `SCW_SECRET_KEY`, `SCW_DEFAULT_PROJECT_ID`
184
+ - **Mailjet**: `MAILJET_API_KEY`, `MAILJET_SECRET_KEY`
185
+ - **ClouDNS**: `CLOUDNS_AUTH_ID`, `CLOUDNS_AUTH_PASSWORD`, `CLOUDNS_SUB_AUTH_ID`, `CLOUDNS_SUB_AUTH_USER`
186
+ - **Docker Hub**: `DOCKER_HUB_USER`, `DOCKER_HUB_TOKEN`
187
+
188
+ ### Basic Usage
189
+
190
+ ```shell
191
+ # Get help for all commands
192
+ granny --help
193
+
194
+ # Analyze AWS resources
195
+ granny analyze vpcs # List all VPCs across regions
196
+ granny analyze lambdas # List all Lambda functions
197
+
198
+ # Manage Bunny CDN
199
+ granny cdn purge 12345 # Purge cache for pull zone 12345
200
+ granny cdn list-zones # List all pull zones
201
+ granny cdn create-zone my-zone # Create a new pull zone
202
+
203
+ # Manage DNS records
204
+ granny dns list --domain example.com --provider cloudflare
205
+
206
+ granny dns add api.example.com --type A --provider hetzner
207
+
208
+ # Manage storage
209
+ granny storage bunny list # List Bunny storage zones
210
+ granny storage aws create my-bucket --website
211
+
212
+ # Build Docker images
213
+ granny docker build-base --image myapp-base --hash-file requirements.txt
214
+
215
+ # Deploy serverless functions
216
+ granny serverless deploy my-function --source-dir ./dist
217
+
218
+ # Manage credentials
219
+ granny credentials status # Show which secrets are configured
220
+ granny credentials get BUNNY_API_KEY
221
+ ```
222
+
223
+ ## CLI Command Reference
224
+
225
+ For detailed documentation of all CLI commands, see the [Documentation](#documentation) section below.
226
+
227
+ ## Use Cases
228
+
229
+ ### 1. Multi-Cloud Infrastructure Analysis
230
+
231
+ Quickly audit your AWS resources across all regions without logging into the console:
232
+
233
+ ```shell
234
+ # List all VPCs with their CIDR blocks and regions
235
+ granny analyze vpcs --json-output
236
+
237
+ # Find all Lambda functions with their runtimes
238
+ granny analyze lambdas --region us-east-1 eu-west-1
239
+ ```
240
+
241
+ ### 2. CDN Management
242
+
243
+ Manage Bunny CDN pull zones, cache, and SSL certificates:
244
+
245
+ ```shell
246
+ # Purge cache for a specific pull zone
247
+ granny cdn purge 12345
248
+
249
+ # List all pull zones
250
+ granny cdn list-zones
251
+
252
+ # Create a new pull zone with origin
253
+ granny cdn create-zone "My Website" --origin-url https://my-origin.com
254
+
255
+ # Add a custom hostname
256
+ granny cdn add-hostname 12345 www.example.com
257
+
258
+ # Request SSL certificate
259
+ granny cdn ssl www.example.com --dns01
260
+ ```
261
+
262
+ ### 3. DNS Management
263
+
264
+ Manage DNS records across multiple providers with a unified interface:
265
+
266
+ ```shell
267
+ # List all records for a domain
268
+ granny dns list --domain example.com --provider cloudflare
269
+
270
+ # Add an A record (auto-detects public IP)
271
+ granny dns add api.example.com --provider hetzner --domain example.com
272
+
273
+ # Add a CNAME record
274
+ granny dns add www.example.com --type CNAME --value example.com --provider bunny
275
+
276
+ # Delete a record
277
+ granny dns delete old.example.com --type A --provider cloudflare --yes
278
+ ```
279
+
280
+ ### 4. Storage Management
281
+
282
+ Work with object storage across AWS S3, Bunny Storage, and Hetzner:
283
+
284
+ ```shell
285
+ # Create a Bunny storage zone
286
+ granny storage bunny create my-assets --region DE
287
+
288
+ # Upload a file
289
+ granny storage bunny upload my-zone ./assets/image.png --password my-password
290
+
291
+ # Create an S3 bucket with website hosting
292
+ granny storage aws create my-website --website
293
+
294
+ # Create a Hetzner S3 bucket
295
+ granny storage hetzner create my-bucket --region fsn1 --public
296
+ ```
297
+
298
+ ### 5. Serverless Deployments
299
+
300
+ Deploy functions to Scaleway FaaS:
301
+
302
+ ```shell
303
+ # Create a namespace
304
+ granny serverless create-namespace my-app
305
+
306
+ # Create a function
307
+ granny serverless create-function my-namespace my-function --runtime node20
308
+
309
+ # Deploy code
310
+ granny serverless deploy my-function --source-dir ./dist --namespace my-app
311
+ ```
312
+
313
+ ### 6. Docker Builds
314
+
315
+ Build and push multi-architecture Docker images:
316
+
317
+ ```shell
318
+ # Build a base image with hash-based tagging
319
+ granny docker build-base \
320
+ --image myapp-base \
321
+ --hash-file requirements.txt \
322
+ --hash-file package.json \
323
+ --secret npmtoken=NPM_TOKEN \
324
+ --login-docker-hub
325
+ ```
326
+
327
+ ### 7. Email Management
328
+
329
+ Configure Mailjet and AWS WorkMail:
330
+
331
+ ```shell
332
+ # Setup Mailjet DNS for a domain
333
+ granny email mailjet setup-dns example.com
334
+
335
+ # Check Mailjet contact status
336
+ granny email mailjet check-contact user@example.com
337
+
338
+ # List WorkMail users
339
+ granny email workmail list-users example.com
340
+
341
+ # Create a WorkMail user
342
+ granny email workmail create-user example.com --email user@example.com --display-name "John Doe"
343
+ ```
344
+
345
+ ## Documentation
346
+
347
+ Full documentation is available in the `docs/` directory:
348
+
349
+ - [📋 CLI Reference](docs/cli.md) — Complete list of all CLI commands
350
+ - [🔍 Analytics](docs/analytics.md) — AWS resource analysis
351
+ - [🚀 CDN Management](docs/cdn.md) — Bunny CDN operations
352
+ - [📦 Create Scripts](docs/create.md) — Infrastructure provisioning
353
+ - [🔐 Credentials](docs/credentials.md) — Secret management
354
+ - [🌐 DNS Management](docs/dns.md) — Domain and record management
355
+ - [🐳 Docker](docs/docker.md) — Multi-arch image builds
356
+ - [⚡ Edge Scripting](docs/edge.md) — Bunny Edge Script management
357
+ - [📧 Email](docs/email.md) — Mailjet and WorkMail
358
+ - [☁️ Serverless](docs/serverless.md) — Scaleway FaaS
359
+ - [💾 Storage](docs/storage.md) — Object storage management
360
+
361
+ ## Standalone Scripts
362
+
363
+ The `granny/create/` directory contains standalone setup scripts for various cloud services:
364
+
365
+ - `setup_aws_cloudfront.py` — Setup AWS CloudFront with security headers
366
+ - `setup_bunny_edge_script.py` — Configure Bunny Edge Scripting
367
+ - `setup_bunny_storage.py` — Setup Bunny Storage
368
+ - `setup_cognito_identity_pool.py` — Create Cognito identity pools
369
+ - `setup_hetzner_bunny.py` — Hetzner + Bunny integration
370
+ - `setup_mailjet_dns.py` — Configure Mailjet DNS settings
371
+ - `setup_private_cdn.py` — Setup private CDN
372
+ - `setup_s3_website.py` — Configure S3 website hosting
373
+ - `setup_scaleway_faas.py` — Setup Scaleway Functions
374
+ - `setup_workmail.py` — Setup AWS WorkMail
375
+ - `manage_mailjet_contacts.py` — Manage Mailjet contacts
376
+ - `auto_certificate.py` — SSL certificate automation
377
+
378
+ Run them directly:
379
+
380
+ ```shell
381
+ python -m granny.create.setup_s3_website example.com --help
382
+ ```
383
+
384
+ Or via the CLI:
385
+
386
+ ```shell
387
+ granny create s3-website example.com --help
388
+ ```
389
+
390
+ ## Project Structure
391
+
392
+ ```
393
+ granny/
394
+ ├── cli/ # CLI command modules
395
+ │ ├── analyze.py # AWS resource analysis
396
+ │ ├── cdn.py # Bunny CDN management
397
+ │ ├── create.py # Infrastructure provisioning
398
+ │ ├── credentials.py # Secret management
399
+ │ ├── docker.py # Docker build commands
400
+ │ ├── dns.py # DNS management
401
+ │ ├── edge.py # Bunny Edge Scripting
402
+ │ ├── email.py # Email management
403
+ │ ├── main.py # CLI entrypoint
404
+ │ ├── serverless.py # Serverless functions
405
+ │ └── storage.py # Storage management
406
+ ├── analyze/ # AWS analysis modules
407
+ ├── cdn/ # CDN provider modules
408
+ ├── create/ # Setup scripts
409
+ ├── credentials/ # Secret management
410
+ ├── dns/ # DNS provider modules
411
+ ├── docker/ # Docker build utilities
412
+ ├── edge/ # Edge Scripting modules
413
+ ├── email/ # Email service modules
414
+ ├── serverless/ # Serverless provider modules
415
+ ├── storage/ # Storage provider modules
416
+ └── report.py # Reporting utilities
417
+ ```
418
+
419
+ ## Contributing
420
+
421
+ 1. Fork the repository
422
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
423
+ 3. Make your changes
424
+ 4. Run tests (`uv run pytest`)
425
+ 5. Commit your changes (`git commit -m 'Add amazing feature'`)
426
+ 6. Push to the branch (`git push origin feature/amazing-feature`)
427
+ 7. Open a Pull Request
428
+
429
+ ## License
430
+
431
+ This project is licensed under the MIT License — see the [LICENSE](LICENSE) file for details.
432
+
433
+ ## Support
434
+
435
+ For issues, questions, or feature requests:
436
+
437
+ - Open an issue on GitHub
438
+ - Check the [documentation](docs/) for detailed guides
439
+ - Run `granny --help` or `granny <command> --help` for CLI usage
440
+
441
+ ---
442
+
443
+ **Granny** — Making cloud infrastructure management simple again.
444
+
445
+ *Built with ❤️ by Martin Wieser and contributors.*
@@ -0,0 +1,68 @@
1
+ granny/__init__.py,sha256=tuWTOmcN8rVNlV25vClCeF4HpB4tLBmNoeyrg6V5k58,554
2
+ granny/report.py,sha256=rm7_-isEzVSlZS1EHHRUehYaedSPY2xO8BpicuK0Xq4,4714
3
+ granny/analyze/__init__.py,sha256=ZEXCUaC66ENaIqNna_k-YkcebBteEt8zijXSpS2Bzv8,211
4
+ granny/analyze/lambdas.py,sha256=qy13sbyjhHUX7rz2Gx630FSpwg8XnGM6qiUmZJaeVkw,1619
5
+ granny/analyze/vpcs.py,sha256=w4e5-rLdXdszFOdQqSIWvszGiWLqfKyh2GXcoYoF57g,1628
6
+ granny/cdn/__init__.py,sha256=_nOhVftvUou1P9loelKRnCoChNjj4fevkyFx1df8yRY,254
7
+ granny/cdn/bunny.py,sha256=z4Wog2x0mpVri-itKGU_cc4i4DIh5wwGkEY8-cq_HOQ,9026
8
+ granny/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ granny/cli/analyze.py,sha256=BXM4jTYMKh9YDaASZHF8nT3ev60_ul9HnTDD5YG_bKU,2144
10
+ granny/cli/cdn.py,sha256=L2UhMVfFljzjQW9o0TY5vk1lfa6-9cn5On096_odzQA,6521
11
+ granny/cli/create.py,sha256=ko5VOgOt_ohq-jEVkiatBn5A0GluX-ffeinokJVLtNY,3085
12
+ granny/cli/credentials.py,sha256=kudEpNWfPEJ6uGqgo2EFQD-fEG1-Jf2J7TnsCthPMhU,2855
13
+ granny/cli/dns.py,sha256=9jZGjPCG3lTMoHrpWDZtF0xrE_aowkpTpL3VcfIQc_c,9339
14
+ granny/cli/docker.py,sha256=Sn2YHNtpXMbVxaxOjBlpSP_YSyFpcYHF5_irF2e_cnE,5109
15
+ granny/cli/edge.py,sha256=cebZ9oCcvqVg8wcM1UOSl7ml3A5LxPkVN02YEYDb32Y,3290
16
+ granny/cli/email.py,sha256=_9vkdgrD9ZEgf0X4r6WhtwHfT9iGwA2sbmwWRMxOt-g,7569
17
+ granny/cli/main.py,sha256=RsdFEdjfWs2CNM73CUyZjYCPGIF_gbEo2-WvXnat6fs,2581
18
+ granny/cli/serverless.py,sha256=cMyRPUPJehHamxx3RVZb-c0_H291Z2cLGyDOZ6L_weI,8657
19
+ granny/cli/storage.py,sha256=dkurWquvPcIXZ55px2zdi3HIMwZYOVf4bMCO8OZGhtM,8292
20
+ granny/create/__init__.py,sha256=t--papKjY8wvH42LzQLoNbAcZ2uBgdW_r4NPdMTONa8,142
21
+ granny/create/auto_certificate.py,sha256=_ongx1T0ExBEjyhNk4BYg_T5WC26u93elixJHHj2drQ,71579
22
+ granny/create/cloudfront-security-headers.js,sha256=S2Nn_o7Xpe3uT4wZt-S03jLEJID1hje9ILrdgvggXrY,1792
23
+ granny/create/manage-dns.sh,sha256=B3d_NwHDngdg0UrbJA6aLIuCyH2WfJEhxh5cyWEB1go,9671
24
+ granny/create/manage_mailjet_contacts.py,sha256=SwIlHygYbTLbiSnCN1ZZQey7ZpVuaRuhyZmMW-5Pr5M,21749
25
+ granny/create/registrars.py,sha256=tawsLlnWAZL9FMEvkUER8UYeplrEfrzmHmiJKCuPPMI,14442
26
+ granny/create/setup_aws_cloudfront.py,sha256=CpfyJrlWy_BoqEXw0kmCcWi8VbYiFFA6mImZuWpsaeo,122338
27
+ granny/create/setup_bunny_edge_script.py,sha256=MsaKW6v3SLiDWSOpNdAkRqcePo49tdKeVI5kCCWzon8,37611
28
+ granny/create/setup_bunny_storage.py,sha256=JJ-lLKBJ6z0hEDYcohlpsYxrFvZiVbbeBSHrRmAmTwA,71516
29
+ granny/create/setup_cognito_identity_pool.py,sha256=AU13YUCBdyPozZ5bbWJlhSF3wDNLQIWi-F2SfPBWZo0,26857
30
+ granny/create/setup_hetzner_bunny.py,sha256=4wxdlCw9uESh0bES56KB4935koDx7tpBGTUVUNb3R-I,58548
31
+ granny/create/setup_mailjet_dns.py,sha256=lzs4axE_6acTse1HZ1GVFmwVZ84R73NxsTIsytF4yrU,45761
32
+ granny/create/setup_private_cdn.py,sha256=F8fwbJGTSvrul2cRxbnJL0oIOgQMwJLd-6eYnSIs8Ts,22733
33
+ granny/create/setup_s3_website.py,sha256=w1PdH1A_4KLtDUpHrQDypMm_FvLUNq67w4Wc82U8KJs,67551
34
+ granny/create/setup_scaleway_faas.py,sha256=bhlDwxtMgaRGnfIDyXKMtzrCfltEFRKm3pw8PccFdKg,45390
35
+ granny/create/setup_workmail.py,sha256=p74M0D4wgymNJTqW9VLQEhMyU9QNxYdIkXx6ITCgyew,46537
36
+ granny/create/www-redirect-function.js,sha256=PIKOFgn7Rb5U53o7CC1P16LaCtmEkHQm0f5nu0Tq_MM,378
37
+ granny/credentials/__init__.py,sha256=4IpBIqDpuo9mNdOyHirKlt9jfS9OVH9SuGj50ldeJKw,332
38
+ granny/credentials/secrets.py,sha256=aK29QZrZGigKuxr1F11u8SkqHNe4TU7UcFVrgfw-Y6E,13140
39
+ granny/dns/__init__.py,sha256=Q1PWXE-4H1NBLxRadWqzcuPkbD4oGskiLAyIAJfL0HY,750
40
+ granny/dns/base.py,sha256=lmzJLM8QHa4j4vmtzzWwjL0GvYDBPZMHT_j1d5mxDWM,3620
41
+ granny/dns/bunny.py,sha256=9GcW6XYQ4cYTBuH05oA085q6GslyswfZQ8UyOKycEX8,5107
42
+ granny/dns/cloudflare.py,sha256=prpoaWjaeLTl0U4Dnq3X2kkbok45V1WqEZJIg-Dg--I,6927
43
+ granny/dns/cloudns.py,sha256=YY5T94caS82ESbcjpehFU7RxxzjX_LZ0tAl4B0E6dzI,5644
44
+ granny/dns/desec.py,sha256=X0IEZq0BDvA7dww78OjhIyqeKze0qZS6ZVYro8Qw3oM,5604
45
+ granny/dns/factory.py,sha256=QPbjWEao9xi4RS084w4kViCRbwQTkJFQ8Oe1BS-KleQ,1726
46
+ granny/dns/hetzner.py,sha256=lv525Xc3oU0QMVNPArEeiCGTS1iXIvNx2zVc8UT9ftk,6179
47
+ granny/dns/manual.py,sha256=zNr2WDOmJ6uyEejolyT_GLRVDTkVPZpA1eSz4-5LKXw,1893
48
+ granny/dns/records.py,sha256=saCk-LEuFC-MTL6td3c7BjFZpxA-rfYY9TignyP8ABQ,972
49
+ granny/docker/__init__.py,sha256=9YjQ39PR92uzDI38q49lvwFVuPux90rGjFgfcRa5QE0,206
50
+ granny/docker/build_base.py,sha256=WRbQk63IiX2eUSrbv_vQ3bOc5D0BB6SShwD82_vPSsk,7135
51
+ granny/edge/__init__.py,sha256=oN3Z80v2Z8oKdIGBGNdO2NYsvzgLJIatZPI3nt2QWU0,145
52
+ granny/edge/bunny.py,sha256=lSkETcSoQoUJTnmeMtcC4v3-WvQhYOKI31tXWDtwDIU,5262
53
+ granny/email/__init__.py,sha256=oP0fMF5lAzX_snY0O6nigNvDhn7vrf9tO1TuXyOIy5E,284
54
+ granny/email/mailjet.py,sha256=2DNxT_FVLSa4131yic80bUpTISqDZvl-RQASp44EdJg,4117
55
+ granny/email/mailjet_contacts.py,sha256=UzJ1zHBi5Goo3ZEPu8pVx3sOfA3E239ofgwIN5KrSDo,4111
56
+ granny/email/ses_forwarding.py,sha256=GBQSzPhxVFhBsmKHeL5QSUO1_m1YaGCq5SiBIOEgp6w,9455
57
+ granny/email/workmail.py,sha256=xTdcwlTQx858YvK4f-u2FBH-JZdZSDvtjKjaJj_kLvw,4824
58
+ granny/serverless/__init__.py,sha256=4DxiVW6-xIeeNdmL9rY0GRydrFTAeLvE-7IW4MiGR34,168
59
+ granny/serverless/scaleway.py,sha256=g6UrvSjwZet0hT2opH20kPmD6Vm-e5yRZeXZVw5lzgQ,9252
60
+ granny/storage/__init__.py,sha256=VeogCcaPcrmQKrl0YWp_Qs2FTgi5zWIq6oiJVjosFBo,285
61
+ granny/storage/aws.py,sha256=WhWS0scIJnRb6YxmitqJa75GPzUkFoKUv1nv2_vUYVs,3639
62
+ granny/storage/bunny.py,sha256=pnT4cX92OCDTdud8-XXCq-E4qKmdGjZy3xgGfnhcmyk,3748
63
+ granny/storage/hetzner.py,sha256=eOmmToxbvZuNBoYuTxKhlY1R-HjaFpwmW7Ph5FSzySA,3872
64
+ granny_devops-0.4.0.dist-info/METADATA,sha256=XygFxW1GII0Tdgdq5cArIpWhdbYN-QUSsHYVncY6XVk,16385
65
+ granny_devops-0.4.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
66
+ granny_devops-0.4.0.dist-info/entry_points.txt,sha256=1ngzseG21f5CERCnMFfZAQWq4cWh-iIIBvlye-YWgTg,47
67
+ granny_devops-0.4.0.dist-info/licenses/LICENSE,sha256=vdnaCidmzVC8OvT2Ekc5HM2HrRgoxS4raCfDMuuD5M0,1091
68
+ granny_devops-0.4.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ granny = granny.cli.main:cli
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Martin Wieser
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.