f-layer 2.0.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 (45) hide show
  1. f_layer-2.0.0.dist-info/METADATA +232 -0
  2. f_layer-2.0.0.dist-info/RECORD +45 -0
  3. f_layer-2.0.0.dist-info/WHEEL +4 -0
  4. f_layer-2.0.0.dist-info/entry_points.txt +2 -0
  5. f_layer-2.0.0.dist-info/licenses/LICENSE +201 -0
  6. flayer/__init__.py +5 -0
  7. flayer/__main__.py +247 -0
  8. flayer/_vpn_guest.py +130 -0
  9. flayer/core/__init__.py +1 -0
  10. flayer/core/config.py +198 -0
  11. flayer/core/contracts.py +114 -0
  12. flayer/core/lifecycle.py +767 -0
  13. flayer/core/state.py +351 -0
  14. flayer/diagnostics/__init__.py +20 -0
  15. flayer/diagnostics/benchmark.py +272 -0
  16. flayer/diagnostics/contracts.py +167 -0
  17. flayer/diagnostics/health.py +154 -0
  18. flayer/diagnostics/reporting.py +24 -0
  19. flayer/extensions/__init__.py +25 -0
  20. flayer/extensions/builtins.py +165 -0
  21. flayer/extensions/contracts.py +196 -0
  22. flayer/extensions/discovery.py +152 -0
  23. flayer/extensions/registry.py +347 -0
  24. flayer/guest.py +270 -0
  25. flayer/integrations/__init__.py +23 -0
  26. flayer/integrations/adapters.py +92 -0
  27. flayer/integrations/contracts.py +241 -0
  28. flayer/integrations/wire.py +273 -0
  29. flayer/profiles/__init__.py +33 -0
  30. flayer/profiles/_amnezia_export.py +176 -0
  31. flayer/profiles/_vless_guest.py +432 -0
  32. flayer/profiles/amneziawg.py +555 -0
  33. flayer/profiles/artifacts.py +609 -0
  34. flayer/profiles/gateway.py +729 -0
  35. flayer/profiles/static_site.py +596 -0
  36. flayer/profiles/vless.py +422 -0
  37. flayer/profiles/vpn.py +524 -0
  38. flayer/providers/__init__.py +1 -0
  39. flayer/providers/contracts.py +207 -0
  40. flayer/providers/lifecycle.py +177 -0
  41. flayer/providers/yandex.py +387 -0
  42. flayer/providers/yandex_lifecycle.py +781 -0
  43. flayer/py.typed +0 -0
  44. flayer/vpn_export.py +72 -0
  45. flayer/vpn_project.py +828 -0
@@ -0,0 +1,781 @@
1
+ """Bounded Yandex mutations with explicit ownership and recoverable uncertain outcomes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ import hashlib
7
+ import ipaddress
8
+ import json
9
+ import os
10
+ import re
11
+ import stat
12
+ import tempfile
13
+ from collections.abc import Mapping
14
+ from pathlib import Path
15
+ from typing import cast
16
+
17
+ from flayer.core.contracts import StackIdentity, ValidateName
18
+
19
+ from .contracts import (
20
+ ProviderCapability,
21
+ ProviderError,
22
+ ProviderErrorCode,
23
+ ProviderResource,
24
+ ResourceKind,
25
+ ResourceReference,
26
+ ValidateIdentifier,
27
+ )
28
+ from .lifecycle import LOGICAL_ID_LABEL, OPERATION_ID_LABEL, JsonValue, MutationError, ResourceSpec
29
+ from .yandex import CommandResult, YandexCloudProvider
30
+
31
+ MAX_USER_DATA_BYTES = 262144
32
+ USERNAME_PATTERN = re.compile(r"[a-z_][a-z0-9_-]{0,31}\Z")
33
+ SHA256_PATTERN = re.compile(r"[a-f0-9]{64}\Z")
34
+ OPERATION_ID_PATTERN = re.compile(r"[a-f0-9]{32}\Z")
35
+ RESOURCE_NAME_PATTERN = re.compile(r"[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?\Z")
36
+ REQUIRED_PARAMETERS: dict[ResourceKind, frozenset[str]] = {
37
+ ResourceKind.NETWORK: frozenset(),
38
+ ResourceKind.SUBNET: frozenset({"zone_id", "ipv4_cidr", "network_dependency"}),
39
+ ResourceKind.SECURITY_GROUP: frozenset({"network_dependency", "rules"}),
40
+ ResourceKind.ADDRESS: frozenset({"zone_id"}),
41
+ ResourceKind.DISK: frozenset({"zone_id", "image_id", "size_gib"}),
42
+ ResourceKind.INSTANCE: frozenset({
43
+ "zone_id", "cores", "memory_gib", "boot_disk_dependency", "subnet_dependency",
44
+ "security_group_dependency", "address_dependency", "ssh_public_key",
45
+ }),
46
+ }
47
+ OPTIONAL_PARAMETERS: dict[ResourceKind, frozenset[str]] = {
48
+ ResourceKind.NETWORK: frozenset(),
49
+ ResourceKind.SUBNET: frozenset(),
50
+ ResourceKind.SECURITY_GROUP: frozenset(),
51
+ ResourceKind.ADDRESS: frozenset(),
52
+ ResourceKind.DISK: frozenset({"type"}),
53
+ ResourceKind.INSTANCE: frozenset({
54
+ "ssh_username", "user_data_file", "user_data_sha256", "platform_id", "core_fraction",
55
+ }),
56
+ }
57
+ DEPENDENCY_KINDS = {
58
+ "network_dependency": ResourceKind.NETWORK,
59
+ "boot_disk_dependency": ResourceKind.DISK,
60
+ "subnet_dependency": ResourceKind.SUBNET,
61
+ "security_group_dependency": ResourceKind.SECURITY_GROUP,
62
+ "address_dependency": ResourceKind.ADDRESS,
63
+ }
64
+ KNOWN_STATUSES = frozenset({
65
+ "UNKNOWN", "ACTIVE", "CREATING", "PROVISIONING", "READY", "RUNNING", "STARTING",
66
+ "STOPPING", "STOPPED", "RESTARTING", "UPDATING", "DELETING", "ERROR", "FAILED",
67
+ })
68
+ USER_DATA_FIELDS = frozenset({
69
+ "ssh_pwauth", "disable_root", "users", "package_update", "package_upgrade", "packages",
70
+ "write_files", "runcmd",
71
+ })
72
+
73
+
74
+ def _String(value: JsonValue) -> str:
75
+ """Require a string without coercion or echoing external input."""
76
+
77
+ if not isinstance(value, str):
78
+ raise ValueError("Lifecycle option requires a string")
79
+
80
+ return value
81
+
82
+
83
+ def _Integer(value: JsonValue, minimum: int, maximum: int) -> int:
84
+ """Bound numeric resource requests while rejecting booleans and floats."""
85
+
86
+ if type(value) is not int or not minimum <= value <= maximum:
87
+ raise ValueError("Lifecycle numeric option exceeds its bounds")
88
+
89
+ return value
90
+
91
+
92
+ def ValidateComputeShape(options: Mapping[str, JsonValue]) -> None:
93
+ """Validate the supported Intel shapes without consulting a cloud account."""
94
+
95
+ cores = _Integer(options["cores"], 2, 32)
96
+ memory = _Integer(options["memory_gib"], 1, 128)
97
+
98
+ if "platform_id" not in options and "core_fraction" not in options:
99
+ return
100
+
101
+ if "platform_id" not in options or "core_fraction" not in options:
102
+ raise ValueError("Specify platform_id and core_fraction together")
103
+
104
+ platform = _String(options["platform_id"])
105
+ fractions = {"standard-v1": (5, 20, 100), "standard-v2": (5, 20, 50, 100),
106
+ "standard-v3": (20, 50, 100)}
107
+ fraction = _Integer(options["core_fraction"], 1, 100)
108
+
109
+ if platform not in fractions or fraction not in fractions[platform]:
110
+ raise ValueError("Unsupported platform_id/core_fraction combination")
111
+
112
+ if _String(options.get("zone_id", "")).startswith("kz1-") and platform != "standard-v3":
113
+ raise ValueError("Kazakhstan deployments require platform_id standard-v3")
114
+
115
+ if fraction == 100:
116
+ valid_cores = (*range(2, 17, 2), 20, 24, 28, 32)
117
+ ratios = tuple(float(value) for value in range(1, 9 if platform == "standard-v1" else 17))
118
+
119
+ else:
120
+ valid_cores = (2, 4)
121
+ ratios = tuple(value / 2 for value in range(1, 5 if fraction == 5 else 9))
122
+
123
+ if platform == "standard-v2" and fraction == 5:
124
+ ratios = (0.25, *ratios)
125
+
126
+ if cores not in valid_cores or memory / cores not in ratios:
127
+ raise ValueError("Unsupported vCPU/RAM combination for the selected platform and core_fraction")
128
+
129
+
130
+ def _PublicKey(value: JsonValue) -> str:
131
+ """Accept a structurally valid public SSH key and discard optional comments."""
132
+
133
+ text = _String(value)
134
+
135
+ if len(text) > 8192 or "\n" in text or "\r" in text:
136
+ raise ValueError("SSH public key must be one bounded OpenSSH line")
137
+
138
+ parts = text.split()
139
+
140
+ if len(parts) < 2 or parts[0] not in {"ssh-ed25519", "ssh-rsa"}:
141
+ raise ValueError("SSH key must use a supported public key algorithm")
142
+
143
+ try:
144
+ content = base64.b64decode(parts[1], validate=True)
145
+ offset = 0
146
+ fields: list[bytes] = []
147
+
148
+ while offset < len(content):
149
+ if len(content) - offset < 4:
150
+ raise ValueError("SSH public key field is truncated")
151
+
152
+ length = int.from_bytes(content[offset:offset + 4], "big")
153
+ offset += 4
154
+
155
+ if length < 1 or offset + length > len(content):
156
+ raise ValueError("SSH public key field is malformed")
157
+
158
+ fields.append(content[offset:offset + length])
159
+ offset += length
160
+
161
+ valid = bool(fields) and fields[0] == parts[0].encode("ascii")
162
+
163
+ if parts[0] == "ssh-ed25519":
164
+ valid = valid and len(fields) == 2 and len(fields[1]) == 32
165
+
166
+ else:
167
+ valid = valid and len(fields) == 3 and 2048 <= int.from_bytes(
168
+ fields[2], "big"
169
+ ).bit_length() <= 16384 and int.from_bytes(fields[1], "big") >= 3
170
+
171
+ if not valid:
172
+ raise ValueError("SSH public key structure is unsupported")
173
+
174
+ except (ValueError, IndexError):
175
+ pass
176
+
177
+ else:
178
+ return " ".join(parts[:2])
179
+
180
+ raise ValueError("SSH public key structure is unsupported")
181
+
182
+
183
+ def _Rules(value: JsonValue) -> tuple[str, ...]:
184
+ """Translate a strict bounded IPv4 rule subset without arbitrary CLI properties."""
185
+
186
+ if not isinstance(value, tuple) or not 1 <= len(value) <= 64:
187
+ raise ValueError("Security rules require 1..64 immutable rules")
188
+
189
+ results: list[str] = []
190
+
191
+ for row in value:
192
+ if not isinstance(row, tuple) or any(
193
+ not isinstance(pair, tuple) or len(pair) != 2 or not isinstance(pair[0], str)
194
+ for pair in row
195
+ ):
196
+ raise ValueError("Security rule requires immutable named pairs")
197
+
198
+ rule = dict(cast(tuple[tuple[str, JsonValue], ...], row))
199
+
200
+ if len(rule) != len(row) or set(rule) - {
201
+ "direction", "protocol", "cidr", "from_port", "to_port"
202
+ } or not {"direction", "protocol", "cidr"} <= set(rule):
203
+ raise ValueError("Security rule contains missing or unsupported options")
204
+
205
+ direction = _String(rule["direction"])
206
+ protocol = _String(rule["protocol"])
207
+
208
+ if direction not in {"ingress", "egress"} or protocol not in {"tcp", "udp", "any"}:
209
+ raise ValueError("Security rule direction or protocol is unsupported")
210
+
211
+ cidr = str(ipaddress.IPv4Network(_String(rule["cidr"]), strict=True))
212
+ result = f"direction={direction},protocol={protocol},v4-cidrs={cidr}"
213
+
214
+ if protocol == "any":
215
+ if "from_port" in rule or "to_port" in rule:
216
+ raise ValueError("Any-protocol rules cannot specify ports")
217
+
218
+ # The CLI requires a port selector even for an unrestricted protocol.
219
+ result += ",from-port=0,to-port=65535"
220
+
221
+ else:
222
+ if not {"from_port", "to_port"} <= set(rule):
223
+ raise ValueError("TCP and UDP rules require an explicit port range")
224
+
225
+ lower = _Integer(rule["from_port"], 1, 65535)
226
+ upper = _Integer(rule["to_port"], lower, 65535)
227
+ result += f",from-port={lower},to-port={upper}"
228
+
229
+ results.append(result)
230
+
231
+ if len(set(results)) != len(results):
232
+ raise ValueError("Security rules must be unique")
233
+
234
+ return tuple(results)
235
+
236
+
237
+ class YandexLifecycleProvider(YandexCloudProvider):
238
+ """Create and delete an explicit six-kind subset using existing read-only observation."""
239
+
240
+ @property
241
+ def Capabilities(self) -> frozenset[ProviderCapability]:
242
+ """Advertise implemented mutations without promising credentials or account permission."""
243
+
244
+ return super().Capabilities | frozenset({
245
+ ProviderCapability.CREATE_RESOURCE, ProviderCapability.DELETE_RESOURCE,
246
+ })
247
+
248
+ def _Run(
249
+ self, arguments: tuple[str, ...], operation: str, scoped: bool = True
250
+ ) -> CommandResult:
251
+ """Sanitize unexpected custom transport failures for reads and mutations alike."""
252
+
253
+ failed = False
254
+
255
+ try:
256
+ result = super()._Run(arguments, operation, scoped)
257
+
258
+ except ProviderError:
259
+ raise
260
+
261
+ except Exception:
262
+ failed = True
263
+
264
+ if failed:
265
+ raise ProviderError(ProviderErrorCode.COMMAND_FAILED, operation)
266
+
267
+ return result
268
+
269
+ def ValidateSpec(self, spec: ResourceSpec) -> None:
270
+ """Validate the complete supported option/dependency subset without cloud access."""
271
+
272
+ failure = False
273
+
274
+ try:
275
+ if not isinstance(spec, ResourceSpec):
276
+ raise ValueError("Lifecycle requests require ResourceSpec")
277
+
278
+ if RESOURCE_NAME_PATTERN.fullmatch(spec.name) is None:
279
+ raise ValueError("Resource name violates provider naming requirements")
280
+
281
+ options = dict(spec.parameters)
282
+ required = REQUIRED_PARAMETERS[spec.kind]
283
+
284
+ if not required <= options.keys() or options.keys() - required - OPTIONAL_PARAMETERS[
285
+ spec.kind
286
+ ]:
287
+ raise ValueError("Unsupported lifecycle options")
288
+
289
+ expected: set[str] = set()
290
+
291
+ for key in options:
292
+ if key in DEPENDENCY_KINDS:
293
+ dependency = _String(options[key])
294
+ ValidateName(dependency, "dependency")
295
+ expected.add(dependency)
296
+
297
+ dependency_options = sum(key in DEPENDENCY_KINDS for key in options)
298
+
299
+ if expected != set(spec.dependencies) or len(expected) != dependency_options:
300
+ raise ValueError("Dependencies must match explicit typed dependency options")
301
+
302
+ if "zone_id" in options:
303
+ ValidateIdentifier(_String(options["zone_id"]))
304
+
305
+ if spec.kind == ResourceKind.SUBNET:
306
+ ipaddress.IPv4Network(_String(options["ipv4_cidr"]), strict=True)
307
+
308
+ elif spec.kind == ResourceKind.SECURITY_GROUP:
309
+ _Rules(options["rules"])
310
+
311
+ elif spec.kind == ResourceKind.DISK:
312
+ ValidateIdentifier(_String(options["image_id"]))
313
+ _Integer(options["size_gib"], 10, 1024)
314
+
315
+ if options.get("type", "network-ssd") not in {"network-ssd", "network-hdd"}:
316
+ raise ValueError("Unsupported boot disk type")
317
+
318
+ elif spec.kind == ResourceKind.INSTANCE:
319
+ ValidateComputeShape(options)
320
+ _PublicKey(options["ssh_public_key"])
321
+
322
+ if USERNAME_PATTERN.fullmatch(_String(options.get("ssh_username", "yc-user"))) is None:
323
+ raise ValueError("Unsupported SSH username")
324
+
325
+ if ("user_data_file" in options) != ("user_data_sha256" in options):
326
+ raise ValueError("Initialization file requires its immutable content digest")
327
+
328
+ if "user_data_file" in options:
329
+ path = Path(_String(options["user_data_file"]))
330
+
331
+ if not str(path) or ".." in path.parts or "\x00" in str(path):
332
+ raise ValueError("Initialization file path is unsafe")
333
+
334
+ if SHA256_PATTERN.fullmatch(_String(options["user_data_sha256"])) is None:
335
+ raise ValueError("Initialization digest must be SHA256")
336
+
337
+ except (ValueError, TypeError, KeyError):
338
+ failure = True
339
+
340
+ if failure:
341
+ raise MutationError(ProviderErrorCode.UNSUPPORTED, "validate-spec", False)
342
+
343
+ def _ValidateIdentity(self, identity: StackIdentity) -> None:
344
+ """Require the selected provider and folder to match the complete stack boundary."""
345
+
346
+ if not isinstance(identity, StackIdentity) or (
347
+ identity.provider != self.Identity.provider_id
348
+ or identity.scope_id != self.Identity.scope_id
349
+ ):
350
+ raise MutationError(ProviderErrorCode.SCOPE_MISMATCH, "identity", False)
351
+
352
+ @staticmethod
353
+ def _ValidateObservation(resource: ProviderResource) -> ProviderResource:
354
+ """Reject opaque vendor status/name text before it reaches orchestration or reports."""
355
+
356
+ failed = resource.status not in KNOWN_STATUSES
357
+
358
+ try:
359
+ ValidateName(resource.name, "observed.name")
360
+
361
+ except ValueError:
362
+ failed = True
363
+
364
+ if failed:
365
+ raise ProviderError(ProviderErrorCode.INVALID_RESPONSE, "observe")
366
+
367
+ return resource
368
+
369
+ def GetResource(self, reference: ResourceReference) -> ProviderResource:
370
+ """Use the scoped base adapter while restricting observed lifecycle fields."""
371
+
372
+ return self._ValidateObservation(super().GetResource(reference))
373
+
374
+ def FindResource(
375
+ self, spec: ResourceSpec, identity: StackIdentity
376
+ ) -> ProviderResource | None:
377
+ """Reconcile exact logical ownership; duplicates and changed desired content conflict."""
378
+
379
+ self.ValidateSpec(spec)
380
+ self._ValidateIdentity(identity)
381
+ labels = identity.OwnershipLabels()
382
+ labels[LOGICAL_ID_LABEL] = spec.logical_id
383
+ ownership = tuple(sorted(labels.items()))
384
+ matches = tuple(
385
+ resource for resource in self.ListResources(spec.kind) if resource.HasLabels(ownership)
386
+ )
387
+
388
+ if len(matches) > 1:
389
+ raise ProviderError(ProviderErrorCode.CONFLICT, "find")
390
+
391
+ if not matches:
392
+ return None
393
+
394
+ resource = self._ValidateObservation(matches[0])
395
+
396
+ if not resource.HasLabels(spec.OwnershipLabels(identity)) or resource.name != spec.name:
397
+ raise ProviderError(ProviderErrorCode.CONFLICT, "find")
398
+
399
+ options = dict(spec.parameters)
400
+
401
+ if "zone_id" in options and self._ResourceZone(resource) != options["zone_id"]:
402
+ raise ProviderError(ProviderErrorCode.SCOPE_MISMATCH, "find")
403
+
404
+ return resource
405
+
406
+ @staticmethod
407
+ def _ResourceZone(resource: ProviderResource) -> str | None:
408
+ """Return the normalized zone; address adapters populate the nested IPv4 zone."""
409
+
410
+ return resource.zone_id
411
+
412
+ def _NormalizeResource(
413
+ self, payload: object, kind: ResourceKind, operation: str
414
+ ) -> ProviderResource:
415
+ """Preserve the documented address allocation zone without storing vendor metadata."""
416
+
417
+ if kind == ResourceKind.ADDRESS and isinstance(payload, dict):
418
+ external = payload.get("external_ipv4_address")
419
+
420
+ if isinstance(external, dict) and "zone_id" in external:
421
+ payload = {**payload, "zone_id": external["zone_id"]}
422
+
423
+ return super()._NormalizeResource(payload, kind, operation)
424
+
425
+ def _Dependencies(
426
+ self, spec: ResourceSpec, identity: StackIdentity,
427
+ dependencies: Mapping[str, ProviderResource],
428
+ ) -> dict[str, ProviderResource]:
429
+ """Refresh every dependency by ID and verify type, scope, complete ownership and zone."""
430
+
431
+ if not isinstance(dependencies, Mapping) or set(dependencies) != set(spec.dependencies):
432
+ raise MutationError(ProviderErrorCode.UNSUPPORTED, "dependencies", False)
433
+
434
+ options = dict(spec.parameters)
435
+ refreshed: dict[str, ProviderResource] = {}
436
+
437
+ for key, kind in DEPENDENCY_KINDS.items():
438
+ if key not in options:
439
+ continue
440
+
441
+ logical_id = _String(options[key])
442
+ resource = dependencies[logical_id]
443
+
444
+ if not isinstance(resource, ProviderResource) or resource.reference.kind != kind:
445
+ raise MutationError(ProviderErrorCode.SCOPE_MISMATCH, "dependencies", False)
446
+
447
+ observed = self.GetResource(resource.reference)
448
+ labels = identity.OwnershipLabels()
449
+ labels[LOGICAL_ID_LABEL] = logical_id
450
+
451
+ if not observed.HasLabels(tuple(sorted(labels.items()))):
452
+ raise MutationError(ProviderErrorCode.SCOPE_MISMATCH, "dependencies", False)
453
+
454
+ if kind in {ResourceKind.SUBNET, ResourceKind.DISK, ResourceKind.ADDRESS} and (
455
+ observed.zone_id != options.get("zone_id")
456
+ ):
457
+ raise MutationError(ProviderErrorCode.SCOPE_MISMATCH, "dependencies", False)
458
+
459
+ if observed.status not in {"UNKNOWN", "ACTIVE", "READY", "RUNNING"}:
460
+ raise MutationError(ProviderErrorCode.CONFLICT, "dependencies", False)
461
+
462
+ refreshed[logical_id] = observed
463
+
464
+ return refreshed
465
+
466
+ @staticmethod
467
+ def _ReadUserData(path: Path, expected_digest: str) -> bytes:
468
+ """Read one owned, private, bounded nonsecret cloud-init JSON artifact without symlinks."""
469
+
470
+ descriptor: int | None = None
471
+ content: bytes | None = None
472
+
473
+ try:
474
+ absolute = path.absolute()
475
+
476
+ for component in (absolute, *absolute.parents):
477
+ if component.is_symlink():
478
+ raise ValueError("Initialization paths cannot contain symbolic links")
479
+
480
+ before = absolute.lstat()
481
+
482
+ if not stat.S_ISREG(before.st_mode):
483
+ raise ValueError("Initialization artifact must be a regular file")
484
+
485
+ descriptor = os.open(
486
+ absolute, os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_NONBLOCK", 0)
487
+ )
488
+ actual = os.fstat(descriptor)
489
+
490
+ if not stat.S_ISREG(actual.st_mode) or (actual.st_dev, actual.st_ino) != (
491
+ before.st_dev, before.st_ino
492
+ ) or not 0 < actual.st_size <= MAX_USER_DATA_BYTES:
493
+ raise ValueError("Initialization artifact is unsafe")
494
+
495
+ if os.name == "posix" and (
496
+ actual.st_uid != os.getuid() or stat.S_IMODE(actual.st_mode) & 0o077
497
+ ):
498
+ raise ValueError("Initialization artifact must be private and owned")
499
+
500
+ with os.fdopen(descriptor, "rb") as source:
501
+ descriptor = None
502
+ data = source.read(MAX_USER_DATA_BYTES + 1)
503
+
504
+ if len(data) > MAX_USER_DATA_BYTES or hashlib.sha256(data).hexdigest() != expected_digest:
505
+ raise ValueError("Initialization artifact content differs from the plan")
506
+
507
+ text = data.decode("utf-8")
508
+
509
+ if text.startswith("#cloud-config\n"):
510
+ text = text[len("#cloud-config\n"):]
511
+
512
+ body = json.loads(text)
513
+
514
+ if not isinstance(body, dict) or set(body) - USER_DATA_FIELDS:
515
+ raise ValueError("Initialization artifact contains unsupported fields")
516
+
517
+ content = data
518
+
519
+ except (OSError, ValueError, RecursionError):
520
+ pass
521
+
522
+ finally:
523
+ if descriptor is not None:
524
+ os.close(descriptor)
525
+
526
+ if content is None:
527
+ raise MutationError(ProviderErrorCode.UNSUPPORTED, "initialization", False)
528
+
529
+ return content
530
+
531
+ def _Arguments(
532
+ self, spec: ResourceSpec, dependencies: Mapping[str, ProviderResource]
533
+ ) -> tuple[str, ...]:
534
+ """Translate only the verified six-kind subset to documented direct CLI arguments."""
535
+
536
+ options = dict(spec.parameters)
537
+ arguments: list[str] = []
538
+
539
+ if spec.kind == ResourceKind.SUBNET:
540
+ network = dependencies[_String(options["network_dependency"])]
541
+ arguments.extend((
542
+ "--network-id", network.reference.resource_id, "--zone", _String(options["zone_id"]),
543
+ "--range", _String(options["ipv4_cidr"]),
544
+ ))
545
+
546
+ elif spec.kind == ResourceKind.SECURITY_GROUP:
547
+ network = dependencies[_String(options["network_dependency"])]
548
+ arguments.extend(("--network-id", network.reference.resource_id))
549
+
550
+ for rule in _Rules(options["rules"]):
551
+ arguments.extend(("--rule", rule))
552
+
553
+ elif spec.kind == ResourceKind.ADDRESS:
554
+ arguments.extend(("--external-ipv4", f"zone={options['zone_id']}"))
555
+
556
+ elif spec.kind == ResourceKind.DISK:
557
+ arguments.extend((
558
+ "--zone", _String(options["zone_id"]), "--source-image-id", _String(options["image_id"]),
559
+ "--size", str(options["size_gib"]), "--type", _String(options.get("type", "network-ssd")),
560
+ ))
561
+
562
+ elif spec.kind == ResourceKind.INSTANCE:
563
+ disk = dependencies[_String(options["boot_disk_dependency"])]
564
+ subnet = dependencies[_String(options["subnet_dependency"])]
565
+ group = dependencies[_String(options["security_group_dependency"])]
566
+ address = dependencies[_String(options["address_dependency"])]
567
+
568
+ if len(address.public_addresses) != 1:
569
+ raise MutationError(ProviderErrorCode.INVALID_RESPONSE, "address", False)
570
+
571
+ interface = (
572
+ f"subnet-id={subnet.reference.resource_id},nat-ip-version=ipv4,"
573
+ f"nat-address={address.public_addresses[0]},"
574
+ f"security-group-ids={group.reference.resource_id}"
575
+ )
576
+ username = _String(options.get("ssh_username", "yc-user"))
577
+ public_key = _PublicKey(options["ssh_public_key"])
578
+ arguments.extend((
579
+ "--zone", _String(options["zone_id"]), "--cores", str(options["cores"]),
580
+ "--memory", str(options["memory_gib"]),
581
+ "--use-boot-disk", f"disk-id={disk.reference.resource_id},auto-delete=false",
582
+ "--network-interface", interface, "--metadata", f"ssh-keys={username}:{public_key}",
583
+ ))
584
+
585
+ if "platform_id" in options:
586
+ arguments.extend(("--platform", _String(options["platform_id"]),
587
+ "--core-fraction", str(options["core_fraction"])))
588
+
589
+ return tuple(arguments)
590
+
591
+ def _CommandFailure(
592
+ self, arguments: tuple[str, ...], result: CommandResult, operation: str,
593
+ ) -> ProviderError:
594
+ """Recognize only proven local parser refusals, never generic RPC error categories."""
595
+
596
+ if result.return_code == 1 and not result.stdout.strip() and operation == "create":
597
+ message = result.stderr.strip()
598
+ unknown_flag = re.fullmatch(r"ERROR: execute command: unknown flag: (--[a-z][a-z0-9-]*)", message)
599
+ rule_rejection = (
600
+ arguments[:3] == ("vpc", "security-group", "create")
601
+ and message == "ERROR: execute command: run command: for rule spec port or [from/to]-port fields is required"
602
+ )
603
+
604
+ if rule_rejection or (unknown_flag is not None and unknown_flag[1] in arguments):
605
+ return MutationError(ProviderErrorCode.UNSUPPORTED, operation, False)
606
+
607
+ return super()._CommandFailure(arguments, result, operation)
608
+
609
+ def _MutationRun(self, arguments: tuple[str, ...], operation: str) -> CommandResult:
610
+ """Never retry a dispatched mutation or retain raw runner exceptions in the error."""
611
+
612
+ code: ProviderErrorCode | None = None
613
+ outcome_unknown = True
614
+
615
+ try:
616
+ result = self._Run(arguments, operation)
617
+
618
+ except ProviderError as error:
619
+ code = error.code
620
+
621
+ if isinstance(error, MutationError):
622
+ outcome_unknown = error.outcome_unknown
623
+
624
+ except Exception:
625
+ code = ProviderErrorCode.COMMAND_FAILED
626
+
627
+ else:
628
+ return result
629
+
630
+ raise MutationError(code or ProviderErrorCode.COMMAND_FAILED, operation, outcome_unknown)
631
+
632
+ def CreateResource(
633
+ self, spec: ResourceSpec, identity: StackIdentity,
634
+ dependencies: Mapping[str, ProviderResource], operation_id: str,
635
+ ) -> ProviderResource:
636
+ """Create once with stable ownership labels and an explicit, separately owned boot disk."""
637
+
638
+ self.ValidateSpec(spec)
639
+ self._ValidateIdentity(identity)
640
+
641
+ if not isinstance(operation_id, str) or OPERATION_ID_PATTERN.fullmatch(operation_id) is None:
642
+ raise MutationError(ProviderErrorCode.UNSUPPORTED, "operation-id", False)
643
+
644
+ options = dict(spec.parameters)
645
+ content: bytes | None = None
646
+ existing: ProviderResource | None = None
647
+ arguments: tuple[str, ...] = ()
648
+ preflight_failure: ProviderErrorCode | None = None
649
+
650
+ try:
651
+ content = self._ReadUserData(
652
+ Path(_String(options["user_data_file"])), _String(options["user_data_sha256"])
653
+ ) if "user_data_file" in options else None
654
+ existing = self.FindResource(spec, identity)
655
+
656
+ if existing is None:
657
+ refreshed = self._Dependencies(spec, identity, dependencies)
658
+ arguments = self._Arguments(spec, refreshed)
659
+
660
+ except MutationError:
661
+ raise
662
+
663
+ except ProviderError as error:
664
+ preflight_failure = error.code
665
+
666
+ except Exception:
667
+ preflight_failure = ProviderErrorCode.COMMAND_FAILED
668
+
669
+ if preflight_failure is not None:
670
+ raise MutationError(preflight_failure, "create", False)
671
+
672
+ if existing is not None:
673
+ return existing
674
+
675
+ creation_labels = (*spec.OwnershipLabels(identity), (OPERATION_ID_LABEL, operation_id))
676
+ labels = ",".join(f"{key}={value}" for key, value in creation_labels)
677
+ command = (*self._CommandForKind(spec.kind), "create", "--name", spec.name, "--labels", labels)
678
+ temporary_path: str | None = None
679
+ mutation_started = False
680
+ failure: ProviderErrorCode | None = None
681
+ created: ProviderResource | None = None
682
+
683
+ try:
684
+ if content is not None:
685
+ with tempfile.NamedTemporaryFile(mode="wb", delete=False) as destination:
686
+ temporary_path = destination.name
687
+ destination.write(content)
688
+ destination.flush()
689
+ os.fsync(destination.fileno())
690
+
691
+ arguments = (*arguments, "--metadata-from-file", f"user-data={temporary_path}")
692
+
693
+ mutation_started = True
694
+ result = self._MutationRun((*command, *arguments), "create")
695
+
696
+ try:
697
+ payload: object = json.loads(result.stdout)
698
+
699
+ except (ValueError, RecursionError):
700
+ failure = ProviderErrorCode.INVALID_RESPONSE
701
+
702
+ else:
703
+ resource = self._ValidateObservation(self._NormalizeResource(payload, spec.kind, "create"))
704
+
705
+ if not resource.HasLabels(creation_labels) or resource.name != spec.name:
706
+ failure = ProviderErrorCode.SCOPE_MISMATCH
707
+
708
+ elif "zone_id" in options and resource.zone_id != options["zone_id"]:
709
+ failure = ProviderErrorCode.SCOPE_MISMATCH
710
+
711
+ else:
712
+ created = resource
713
+
714
+ except MutationError:
715
+ raise
716
+
717
+ except ProviderError as error:
718
+ failure = error.code
719
+
720
+ except (OSError, ValueError):
721
+ failure = ProviderErrorCode.COMMAND_FAILED
722
+
723
+ finally:
724
+ if temporary_path is not None:
725
+ try:
726
+ os.unlink(temporary_path)
727
+
728
+ except FileNotFoundError:
729
+ pass
730
+
731
+ except OSError:
732
+ failure = ProviderErrorCode.COMMAND_FAILED
733
+
734
+ if failure is None and created is not None:
735
+ return created
736
+
737
+ raise MutationError(failure or ProviderErrorCode.INVALID_RESPONSE, "create", mutation_started)
738
+
739
+ def DeleteResource(
740
+ self, reference: ResourceReference, identity: StackIdentity, logical_id: str,
741
+ operation_id: str | None = None,
742
+ ) -> None:
743
+ """Observe and verify ownership before exact-ID deletion, then confirm absence once."""
744
+
745
+ self._ValidateIdentity(identity)
746
+ ValidateName(logical_id, "delete.logical_id")
747
+ labels = identity.OwnershipLabels()
748
+ labels[LOGICAL_ID_LABEL] = logical_id
749
+
750
+ if operation_id is not None:
751
+ if not isinstance(operation_id, str) or OPERATION_ID_PATTERN.fullmatch(operation_id) is None:
752
+ raise MutationError(ProviderErrorCode.UNSUPPORTED, "operation-id", False)
753
+
754
+ labels[OPERATION_ID_LABEL] = operation_id
755
+
756
+ try:
757
+ resource = self.GetResource(reference)
758
+
759
+ except ProviderError as error:
760
+ if error.code == ProviderErrorCode.NOT_FOUND:
761
+ return
762
+
763
+ raise
764
+
765
+ if not resource.HasLabels(tuple(sorted(labels.items()))):
766
+ raise MutationError(ProviderErrorCode.SCOPE_MISMATCH, "delete", False)
767
+
768
+ command = self._CommandForKind(reference.kind)
769
+ self._MutationRun((*command, "delete", "--id", reference.resource_id), "delete")
770
+ failure = ProviderErrorCode.CONFLICT
771
+
772
+ try:
773
+ self.GetResource(reference)
774
+
775
+ except ProviderError as error:
776
+ if error.code == ProviderErrorCode.NOT_FOUND:
777
+ return
778
+
779
+ failure = error.code
780
+
781
+ raise MutationError(failure, "delete", True)