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.
- granny/__init__.py +19 -0
- granny/analyze/__init__.py +6 -0
- granny/analyze/lambdas.py +59 -0
- granny/analyze/vpcs.py +57 -0
- granny/cdn/__init__.py +9 -0
- granny/cdn/bunny.py +231 -0
- granny/cli/__init__.py +0 -0
- granny/cli/analyze.py +66 -0
- granny/cli/cdn.py +210 -0
- granny/cli/create.py +94 -0
- granny/cli/credentials.py +99 -0
- granny/cli/dns.py +290 -0
- granny/cli/docker.py +165 -0
- granny/cli/edge.py +106 -0
- granny/cli/email.py +224 -0
- granny/cli/main.py +98 -0
- granny/cli/serverless.py +278 -0
- granny/cli/storage.py +249 -0
- granny/create/__init__.py +4 -0
- granny/create/auto_certificate.py +1899 -0
- granny/create/cloudfront-security-headers.js +53 -0
- granny/create/manage-dns.sh +321 -0
- granny/create/manage_mailjet_contacts.py +619 -0
- granny/create/registrars.py +363 -0
- granny/create/setup_aws_cloudfront.py +2808 -0
- granny/create/setup_bunny_edge_script.py +923 -0
- granny/create/setup_bunny_storage.py +1719 -0
- granny/create/setup_cognito_identity_pool.py +740 -0
- granny/create/setup_hetzner_bunny.py +1482 -0
- granny/create/setup_mailjet_dns.py +1103 -0
- granny/create/setup_private_cdn.py +547 -0
- granny/create/setup_s3_website.py +1512 -0
- granny/create/setup_scaleway_faas.py +1165 -0
- granny/create/setup_workmail.py +1217 -0
- granny/create/www-redirect-function.js +17 -0
- granny/credentials/__init__.py +15 -0
- granny/credentials/secrets.py +403 -0
- granny/dns/__init__.py +22 -0
- granny/dns/base.py +113 -0
- granny/dns/bunny.py +150 -0
- granny/dns/cloudflare.py +192 -0
- granny/dns/cloudns.py +162 -0
- granny/dns/desec.py +152 -0
- granny/dns/factory.py +72 -0
- granny/dns/hetzner.py +165 -0
- granny/dns/manual.py +64 -0
- granny/dns/records.py +29 -0
- granny/docker/__init__.py +5 -0
- granny/docker/build_base.py +204 -0
- granny/edge/__init__.py +5 -0
- granny/edge/bunny.py +147 -0
- granny/email/__init__.py +7 -0
- granny/email/mailjet.py +119 -0
- granny/email/mailjet_contacts.py +115 -0
- granny/email/ses_forwarding.py +281 -0
- granny/email/workmail.py +145 -0
- granny/report.py +128 -0
- granny/serverless/__init__.py +5 -0
- granny/serverless/scaleway.py +264 -0
- granny/storage/__init__.py +7 -0
- granny/storage/aws.py +113 -0
- granny/storage/bunny.py +98 -0
- granny/storage/hetzner.py +118 -0
- granny_devops-0.4.0.dist-info/METADATA +445 -0
- granny_devops-0.4.0.dist-info/RECORD +68 -0
- granny_devops-0.4.0.dist-info/WHEEL +4 -0
- granny_devops-0.4.0.dist-info/entry_points.txt +2 -0
- 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,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.
|