@mriekki/project-forge 4.10.2 → 4.10.3
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.
- package/assets/components.json +2 -2
- package/assets/generated/ASSEMBLY.json +4 -4
- package/assets/generated/deployment/src/project_forge_deployment/cloudflare_ingress.py +31 -21
- package/assets/generated/deployment/src/project_forge_deployment/cloudflare_route_apply.py +4 -15
- package/package.json +1 -1
package/assets/components.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@mriekki/project-forge",
|
|
4
|
-
"version": "4.10.
|
|
4
|
+
"version": "4.10.3",
|
|
5
5
|
"defaultProfile": "greenfield-project",
|
|
6
6
|
"profiles": {
|
|
7
7
|
"greenfield-project": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"canonicalDescriptor": "distributions/greenfield-workspace.json"
|
|
22
22
|
},
|
|
23
23
|
"deployment": {
|
|
24
|
-
"revision": "
|
|
24
|
+
"revision": "75c272db383f1803383e8cd4ad4511537c175abe",
|
|
25
25
|
"distribution": "greenfield-project",
|
|
26
26
|
"descriptor": "assets/deployment/distribution.json",
|
|
27
27
|
"sourceRoot": "assets/generated/deployment",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@mriekki/project-forge",
|
|
4
|
-
"version": "4.10.
|
|
4
|
+
"version": "4.10.3",
|
|
5
5
|
"components": {
|
|
6
6
|
"core": {
|
|
7
7
|
"revision": "8796fd8dc22df8b64078e5b460e1008a8c7a5f7d",
|
|
@@ -326,7 +326,7 @@
|
|
|
326
326
|
]
|
|
327
327
|
},
|
|
328
328
|
"deployment": {
|
|
329
|
-
"revision": "
|
|
329
|
+
"revision": "75c272db383f1803383e8cd4ad4511537c175abe",
|
|
330
330
|
"distribution": "greenfield-project",
|
|
331
331
|
"files": [
|
|
332
332
|
{
|
|
@@ -402,7 +402,7 @@
|
|
|
402
402
|
{
|
|
403
403
|
"source": "src/project_forge_deployment/cloudflare_ingress.py",
|
|
404
404
|
"destination": "tools/project-forge-deployment/project_forge_deployment/cloudflare_ingress.py",
|
|
405
|
-
"sha256": "
|
|
405
|
+
"sha256": "ca2c80e0b64792f550a08fc51994bdfdac42644c35f23cdf7ae5c50b468109f8"
|
|
406
406
|
},
|
|
407
407
|
{
|
|
408
408
|
"source": "src/project_forge_deployment/cloudflare_ingress_remote.py",
|
|
@@ -412,7 +412,7 @@
|
|
|
412
412
|
{
|
|
413
413
|
"source": "src/project_forge_deployment/cloudflare_route_apply.py",
|
|
414
414
|
"destination": "tools/project-forge-deployment/project_forge_deployment/cloudflare_route_apply.py",
|
|
415
|
-
"sha256": "
|
|
415
|
+
"sha256": "d3fe77a2f61ae281308ad1046ee1e6e5ea460f3605c5cd275cc16cff3e9bb568"
|
|
416
416
|
},
|
|
417
417
|
{
|
|
418
418
|
"source": "src/project_forge_deployment/cli.py",
|
|
@@ -37,6 +37,11 @@ PUBLIC_INGRESS_ROUTE_WRITE_PHRASE = "[PUBLIC INGRESS ROUTE WRITE]"
|
|
|
37
37
|
PUBLIC_INGRESS_ROUTE_REMOVE_PHRASE = "[PUBLIC INGRESS ROUTE REMOVE]"
|
|
38
38
|
CLOUDFLARE_API_TOKEN_ENV = "PROJECT_FORGE_CLOUDFLARE_API_TOKEN"
|
|
39
39
|
|
|
40
|
+
|
|
41
|
+
class CloudflarePostWriteVerificationError(DeploymentKitError):
|
|
42
|
+
"""Final public-route verification failed after mutations completed."""
|
|
43
|
+
|
|
44
|
+
|
|
40
45
|
_SLUG_RE = re.compile(r"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$")
|
|
41
46
|
_REPO_RE = re.compile(r"^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$")
|
|
42
47
|
_LABEL_RE = re.compile(r"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$")
|
|
@@ -698,27 +703,32 @@ def apply_public_route(plan: dict[str, Any], *, api_token: str) -> dict[str, Any
|
|
|
698
703
|
pass
|
|
699
704
|
raise
|
|
700
705
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
706
|
+
try:
|
|
707
|
+
final_inventory = cloudflare_route_inventory(name, scope)
|
|
708
|
+
if _find_route(final_inventory, spec["routeId"]) != spec:
|
|
709
|
+
raise DeploymentKitError("Public route registration returned without exact host ownership metadata.")
|
|
710
|
+
final_config = cloudflare_api.get_tunnel_configuration(api_token, host["accountId"], host["tunnelId"])
|
|
711
|
+
if final_config != plan["desiredTunnelConfig"]:
|
|
712
|
+
raise DeploymentKitError("Cloudflare tunnel configuration does not match the reviewed route plan after apply.")
|
|
713
|
+
final_dns = _exact_dns_record(
|
|
714
|
+
cloudflare_api.list_dns_records(api_token, host["zoneId"], name=spec["publicHostname"]),
|
|
715
|
+
spec=spec,
|
|
716
|
+
tunnel_id=host["tunnelId"],
|
|
717
|
+
)
|
|
718
|
+
if final_dns is None:
|
|
719
|
+
raise DeploymentKitError("Cloudflare DNS record is missing after public-route apply.")
|
|
720
|
+
return {
|
|
721
|
+
"targetName": name,
|
|
722
|
+
"scope": scope,
|
|
723
|
+
"route": spec,
|
|
724
|
+
"dnsRecordId": str(final_dns["id"]),
|
|
725
|
+
"publicUrl": f"https://{spec['publicHostname']}",
|
|
726
|
+
"mutated": True,
|
|
727
|
+
}
|
|
728
|
+
except Exception as error:
|
|
729
|
+
raise CloudflarePostWriteVerificationError(
|
|
730
|
+
f"Cloudflare public-route post-write verification failed: {error}"
|
|
731
|
+
) from error
|
|
722
732
|
|
|
723
733
|
|
|
724
734
|
def plan_public_route_removal(
|
|
@@ -17,12 +17,6 @@ from .errors import DeploymentKitError
|
|
|
17
17
|
from .profiles import load_profile
|
|
18
18
|
from .ssh import verify_access
|
|
19
19
|
|
|
20
|
-
_FINAL_VERIFICATION_ERRORS = {
|
|
21
|
-
"Public route registration returned without exact host ownership metadata.",
|
|
22
|
-
"Cloudflare tunnel configuration does not match the reviewed route plan after apply.",
|
|
23
|
-
"Cloudflare DNS record is missing after public-route apply.",
|
|
24
|
-
}
|
|
25
|
-
|
|
26
20
|
|
|
27
21
|
def _reviewed_config_matches(actual: Any, reviewed: Any) -> bool:
|
|
28
22
|
"""Match reviewed config after removing only the observed safe provider default."""
|
|
@@ -131,19 +125,14 @@ def apply_public_route(plan: dict[str, Any], *, api_token: str) -> dict[str, Any
|
|
|
131
125
|
"""Apply a reviewed public route with bounded post-write recovery.
|
|
132
126
|
|
|
133
127
|
The underlying apply retains all pre-write stale-state checks and its write
|
|
134
|
-
rollback.
|
|
135
|
-
|
|
128
|
+
rollback. Recovery is entered only for the typed error emitted by the
|
|
129
|
+
post-write verification phase, so preflight failures can never be turned
|
|
136
130
|
into success by observing later provider state.
|
|
137
131
|
"""
|
|
138
132
|
try:
|
|
139
133
|
return ingress.apply_public_route(plan, api_token=api_token)
|
|
140
|
-
except
|
|
141
|
-
if (
|
|
142
|
-
not isinstance(original_error, DeploymentKitError)
|
|
143
|
-
or str(original_error) not in _FINAL_VERIFICATION_ERRORS
|
|
144
|
-
or not isinstance(plan, dict)
|
|
145
|
-
or plan.get("action") != "register"
|
|
146
|
-
):
|
|
134
|
+
except ingress.CloudflarePostWriteVerificationError as original_error:
|
|
135
|
+
if not isinstance(plan, dict) or plan.get("action") != "register":
|
|
147
136
|
raise
|
|
148
137
|
required = {
|
|
149
138
|
"targetName", "scope", "route", "previousRoute", "tunnelConfig",
|