granny-devops 0.17.2__tar.gz → 0.17.3__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.
- {granny_devops-0.17.2 → granny_devops-0.17.3}/PKG-INFO +1 -1
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/__init__.py +1 -1
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/cli/dns.py +21 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/dns/base.py +12 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/dns/cloudflare.py +60 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/pyproject.toml +1 -1
- {granny_devops-0.17.2 → granny_devops-0.17.3}/.gitignore +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/LICENSE +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/README.md +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/analyze/__init__.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/analyze/costs.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/analyze/credits.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/analyze/gpu_pricing.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/analyze/gpus.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/analyze/kubernetes.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/analyze/lambdas.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/analyze/vpcs.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/authentik/__init__.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/authentik/client.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/authentik/provision.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/aws/__init__.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/aws/bedrock.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/aws/quota.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/azure/__init__.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/azure/_client.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/azure/account.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/azure/deployment.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/azure/openai.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/azure/quota.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/azure/resourcegroup.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/azure/role.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/azure/sku.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/azure/vm.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/azure/webapp.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/cdn/__init__.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/cdn/bunny.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/cli/__init__.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/cli/analyze.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/cli/authentik.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/cli/aws.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/cli/azure.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/cli/cdn.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/cli/cloudflare.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/cli/create.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/cli/credentials.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/cli/docker.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/cli/edge.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/cli/elk.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/cli/email.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/cli/indexing.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/cli/main.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/cli/oauth.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/cli/serverless.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/cli/storage.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/cli/zitadel.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/cloudflare/__init__.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/cloudflare/d1.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/cloudflare/r2.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/cloudflare/workers.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/create/__init__.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/create/auto_certificate.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/create/cloudfront-security-headers.js +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/create/letsencrypt_cert.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/create/manage-dns.sh +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/create/manage_mailjet_contacts.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/create/registrars.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/create/setup_aws_cloudfront.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/create/setup_bunny_edge_script.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/create/setup_bunny_storage.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/create/setup_cognito_identity_pool.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/create/setup_hetzner_bunny.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/create/setup_mailjet_dns.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/create/setup_private_cdn.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/create/setup_s3_website.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/create/setup_scaleway_container.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/create/setup_scaleway_faas.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/create/setup_workmail.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/create/www-redirect-function.js +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/credentials/__init__.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/credentials/secrets.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/dns/__init__.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/dns/bunny.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/dns/cloudns.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/dns/desec.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/dns/factory.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/dns/hetzner.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/dns/inwx.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/dns/manual.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/dns/records.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/docker/__init__.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/docker/build_base.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/edge/__init__.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/edge/bunny.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/elk/__init__.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/elk/client.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/email/__init__.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/email/mailjet.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/email/mailjet_contacts.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/email/ses_forwarding.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/email/stalwart.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/email/workmail.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/indexing/__init__.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/indexing/google.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/indexing/indexnow.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/report.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/serverless/__init__.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/serverless/scaleway.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/storage/__init__.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/storage/aws.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/storage/bunny.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/storage/hetzner.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/zitadel/__init__.py +0 -0
- {granny_devops-0.17.2 → granny_devops-0.17.3}/granny/zitadel/client.py +0 -0
|
@@ -145,6 +145,27 @@ def dns() -> None:
|
|
|
145
145
|
"""Provider-agnostic DNS record management."""
|
|
146
146
|
|
|
147
147
|
|
|
148
|
+
@dns.command("create-zone")
|
|
149
|
+
@click.argument("zone")
|
|
150
|
+
@provider_option
|
|
151
|
+
def create_zone_cmd(zone: str, provider: str | None) -> None:
|
|
152
|
+
"""Create zone ZONE at the provider (idempotent).
|
|
153
|
+
|
|
154
|
+
Prints the assigned nameservers — point the registrar's NS records at
|
|
155
|
+
them to activate the zone. Cloudflare requires a token with the
|
|
156
|
+
account-level ``Zone: Create`` permission.
|
|
157
|
+
"""
|
|
158
|
+
zone = zone.strip().rstrip(".").lower()
|
|
159
|
+
if not zone or "." not in zone:
|
|
160
|
+
raise click.UsageError(f"ZONE must be a registrable domain, got {zone!r}")
|
|
161
|
+
dns_provider = _provider_instance(provider)
|
|
162
|
+
try:
|
|
163
|
+
meta = dns_provider.create_zone(zone)
|
|
164
|
+
except NotImplementedError as exc:
|
|
165
|
+
raise click.ClickException(str(exc))
|
|
166
|
+
click.echo(json.dumps(meta, indent=2))
|
|
167
|
+
|
|
168
|
+
|
|
148
169
|
@dns.command("list")
|
|
149
170
|
@provider_option
|
|
150
171
|
@domain_option
|
|
@@ -33,6 +33,18 @@ class DNSProvider(ABC):
|
|
|
33
33
|
f"{type(self).__name__} does not expose nameservers via API"
|
|
34
34
|
)
|
|
35
35
|
|
|
36
|
+
def create_zone(self, zone_name: str) -> dict:
|
|
37
|
+
"""Create ``zone_name`` at the provider (idempotent where supported).
|
|
38
|
+
|
|
39
|
+
Returns provider metadata for the zone — at minimum ``id``, ``name``,
|
|
40
|
+
``status``, and ``nameservers`` (the pair the registrar must delegate
|
|
41
|
+
to). Providers whose API has no zone-creation endpoint (or where zones
|
|
42
|
+
are registrar-managed) keep this default.
|
|
43
|
+
"""
|
|
44
|
+
raise NotImplementedError(
|
|
45
|
+
f"{type(self).__name__} does not support zone creation via API"
|
|
46
|
+
)
|
|
47
|
+
|
|
36
48
|
@abstractmethod
|
|
37
49
|
def list_records(
|
|
38
50
|
self,
|
|
@@ -71,6 +71,66 @@ class CloudflareDNSProvider(DNSProvider):
|
|
|
71
71
|
return []
|
|
72
72
|
return list(result[0].get("name_servers") or [])
|
|
73
73
|
|
|
74
|
+
def _account_id(self) -> str:
|
|
75
|
+
"""Resolve the account id the token operates on.
|
|
76
|
+
|
|
77
|
+
``GET /accounts`` needs the Account Settings read scope, which
|
|
78
|
+
zone-scoped tokens usually lack — fall back to the ``account`` stamped
|
|
79
|
+
on any zone the token can already see.
|
|
80
|
+
"""
|
|
81
|
+
try:
|
|
82
|
+
body = self._request("GET", "/accounts", params={"per_page": 1})
|
|
83
|
+
result = body.get("result") or []
|
|
84
|
+
if result:
|
|
85
|
+
return result[0]["id"]
|
|
86
|
+
except RuntimeError:
|
|
87
|
+
pass
|
|
88
|
+
body = self._request("GET", "/zones", params={"per_page": 1})
|
|
89
|
+
result = body.get("result") or []
|
|
90
|
+
if not result:
|
|
91
|
+
raise RuntimeError(
|
|
92
|
+
"Cloudflare: cannot resolve account id — token sees no "
|
|
93
|
+
"accounts and no zones"
|
|
94
|
+
)
|
|
95
|
+
return result[0]["account"]["id"]
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def _zone_meta(zone: dict) -> dict:
|
|
99
|
+
return {
|
|
100
|
+
"id": zone.get("id"),
|
|
101
|
+
"name": zone.get("name"),
|
|
102
|
+
"status": zone.get("status"),
|
|
103
|
+
"nameservers": list(zone.get("name_servers") or []),
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
def create_zone(self, zone_name: str) -> dict:
|
|
107
|
+
"""Create ``zone_name`` in the token's account (idempotent).
|
|
108
|
+
|
|
109
|
+
Requires the account-level ``Zone: Create`` token permission; without
|
|
110
|
+
it Cloudflare answers 403 ``com.cloudflare.api.account.zone.create``.
|
|
111
|
+
If the zone already exists in this account, returns it unchanged.
|
|
112
|
+
"""
|
|
113
|
+
existing = self._request("GET", "/zones", params={"name": zone_name})
|
|
114
|
+
result = existing.get("result") or []
|
|
115
|
+
if result:
|
|
116
|
+
logger.info("Cloudflare: zone %s already exists", zone_name)
|
|
117
|
+
return self._zone_meta(result[0])
|
|
118
|
+
body = self._request(
|
|
119
|
+
"POST",
|
|
120
|
+
"/zones",
|
|
121
|
+
json={
|
|
122
|
+
"name": zone_name,
|
|
123
|
+
"account": {"id": self._account_id()},
|
|
124
|
+
"type": "full",
|
|
125
|
+
},
|
|
126
|
+
)
|
|
127
|
+
zone = body.get("result") or {}
|
|
128
|
+
self._zone_cache[zone_name] = zone.get("id", "")
|
|
129
|
+
logger.info(
|
|
130
|
+
"Cloudflare: created zone %s (status %s)", zone_name, zone.get("status")
|
|
131
|
+
)
|
|
132
|
+
return self._zone_meta(zone)
|
|
133
|
+
|
|
74
134
|
def _fqdn(self, zone_id: str, name: str) -> str:
|
|
75
135
|
# Cloudflare's record API uses full FQDN for `name`. The caller passes
|
|
76
136
|
# us a short name relative to the zone, so we need the zone name to
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|