Перейти к содержанию

flayer.profiles

Module source

flayer.profiles

Explicit deployment profiles and owned generated artifacts.

Artifact dataclass

Artifact(name: str, content: bytes, sensitive: bool = True)

Memory-only generated bytes; sensitive payloads are never included in repr.

name instance-attribute

name: str

content class-attribute instance-attribute

content: bytes = field(repr=False)

sensitive class-attribute instance-attribute

sensitive: bool = True

ArtifactBundle dataclass

ArtifactBundle(identity: StackIdentity, kind: str, name: str, files: tuple[Artifact, ...])

A named set owned by one complete stack identity, independent of provider state.

identity instance-attribute

identity: StackIdentity

kind instance-attribute

kind: str

name instance-attribute

name: str

files class-attribute instance-attribute

files: tuple[Artifact, ...] = field(repr=False)

ArtifactError

Bases: ContractError

An artifact is invalid, foreign, changed, busy, or unavailable for safe storage.

GatewayDevice dataclass

GatewayDevice(device_id: str, public_key: str, source_cidrs: tuple[str, ...])

One device public credential and explicitly bounded connection source networks.

device_id instance-attribute

device_id: str

public_key class-attribute instance-attribute

public_key: str = field(repr=False)

source_cidrs instance-attribute

source_cidrs: tuple[str, ...]

GatewayError

Bases: ContractError

Profile intent or a prepared initialization artifact violates its contract.

GatewayPlan dataclass

GatewayPlan(profile: GatewayProfile)

Immutable composition input; serialized lifecycle plans bind an exported artifact.

profile instance-attribute

profile: GatewayProfile

GatewayPort dataclass

GatewayPort(protocol: str, port: int, cidrs: tuple[str, ...])

One explicit service allowance; an allowance does not install a listener.

protocol instance-attribute

protocol: str

port instance-attribute

port: int

cidrs instance-attribute

cidrs: tuple[str, ...]

GatewayProfile dataclass

GatewayProfile(identity: StackIdentity, zone_id: str, image_id: str, subnet_cidr: str, ssh_public_key: str, management_cidrs: tuple[str, ...], transport: GatewayTransport, service_ports: tuple[GatewayPort, ...] = (), ssh_username: str = 'gateway-admin', ssh_port: int = 22, cores: int = 2, memory_gib: int = 2, boot_disk_gib: int = 20, schema_version: int = PROFILE_SCHEMA_VERSION)

Explicit cloud placement and Ubuntu 24.04 cloud-init security intent.

identity instance-attribute

identity: StackIdentity

zone_id instance-attribute

zone_id: str

image_id instance-attribute

image_id: str

subnet_cidr instance-attribute

subnet_cidr: str

ssh_public_key class-attribute instance-attribute

ssh_public_key: str = field(repr=False)

management_cidrs instance-attribute

management_cidrs: tuple[str, ...]

transport instance-attribute

transport: GatewayTransport

service_ports class-attribute instance-attribute

service_ports: tuple[GatewayPort, ...] = ()

ssh_username class-attribute instance-attribute

ssh_username: str = 'gateway-admin'

ssh_port class-attribute instance-attribute

ssh_port: int = 22

cores class-attribute instance-attribute

cores: int = 2

memory_gib class-attribute instance-attribute

memory_gib: int = 2

boot_disk_gib class-attribute instance-attribute

boot_disk_gib: int = 20

schema_version class-attribute instance-attribute

schema_version: int = PROFILE_SCHEMA_VERSION

GatewayTarget dataclass

GatewayTarget(name: str, host: str, port: int)

One exact TCP destination reachable through the SSH gateway.

name instance-attribute

name: str

host instance-attribute

host: str

port instance-attribute

port: int

GatewayTransport dataclass

GatewayTransport(username: str, targets: tuple[GatewayTarget, ...], devices: tuple[GatewayDevice, ...])

A restricted SSH local-forward transport with separately declared device keys.

username instance-attribute

username: str

targets instance-attribute

targets: tuple[GatewayTarget, ...]

devices instance-attribute

devices: tuple[GatewayDevice, ...]

BuildDeviceBundle

BuildDeviceBundle(identity: StackIdentity, device_id: str, transport: str, files: tuple[Artifact, ...]) -> ArtifactBundle

Wrap externally issued transport files without generating or validating credentials.

Source code in installed/flayer/profiles/artifacts.py
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
def BuildDeviceBundle(
    identity: StackIdentity, device_id: str, transport: str, files: tuple[Artifact, ...],
) -> ArtifactBundle:
    """Wrap externally issued transport files without generating or validating credentials."""

    ValidateName(transport, "transport")
    ValidateName(device_id, "device_id")

    if not isinstance(files, tuple) or not 1 <= len(files) < MAX_BUNDLE_FILES or any(
        not isinstance(item, Artifact) for item in files
    ):
        raise ArtifactError("Device files must contain 1..7 immutable Artifact values")

    if any(not item.sensitive for item in files):
        raise ArtifactError("Externally issued device profiles must be marked sensitive")

    descriptor = Artifact("device-info.json", (json.dumps({
        "schema_version": 1, "transport": transport, "device_id": device_id,
        "status": "externally-issued-unverified",
    }, sort_keys=True) + "\n").encode("utf-8"), sensitive=False)

    return ArtifactBundle(identity, "device", device_id, files + (descriptor,))

RemoveArtifactBundle

RemoveArtifactBundle(root: str | Path, identity: StackIdentity, *, kind: str, name: str) -> bool

Remove only exact, unchanged owned files; never recurse or delete provider resources.

Source code in installed/flayer/profiles/artifacts.py
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
def RemoveArtifactBundle(
    root: str | Path, identity: StackIdentity, *, kind: str, name: str,
) -> bool:
    """Remove only exact, unchanged owned files; never recurse or delete provider resources."""

    if not isinstance(identity, StackIdentity) or not isinstance(kind, str) or kind not in {"server", "device"}:
        raise ArtifactError("Artifact removal requires explicit identity and bundle kind")

    ValidateName(name, "artifact name")
    root_path = Path(root)
    bundle_name = f"{kind}-{name}"

    try:
        _ValidatePath(root_path)

        if not root_path.exists():
            return False

        with _Directory(root_path) as parent:
            with _BundleLock(parent, bundle_name):
                try:
                    descriptor = os.open(bundle_name, os.O_RDONLY | getattr(os, "O_DIRECTORY", 0) | getattr(os, "O_NOFOLLOW", 0), dir_fd=parent)

                except FileNotFoundError:
                    return False

                try:
                    _RequireOwned(os.fstat(descriptor), directory=True)
                    snapshot = _ValidateManifest(descriptor, identity, kind, name)
                    _AssertDirectory(parent, bundle_name, descriptor)

                    for file in snapshot.files:
                        _AssertFile(descriptor, file.name, file.receipt)

                    _AssertFile(descriptor, _MANIFEST_NAME, snapshot.manifest)

                    for file in snapshot.files:
                        _AssertDirectory(parent, bundle_name, descriptor)
                        _UnlinkFile(descriptor, file.name, file.receipt)

                    _UnlinkFile(descriptor, _MANIFEST_NAME, snapshot.manifest)
                    os.fsync(descriptor)
                    _AssertDirectory(parent, bundle_name, descriptor)
                    os.rmdir(bundle_name, dir_fd=parent)

                finally:
                    os.close(descriptor)

                os.fsync(parent)

    except (OSError, ValueError, RecursionError):
        raise ArtifactError("Unable to remove a valid owned artifact bundle; partial cleanup may require recovery") from None

    return True

WriteArtifactBundle

WriteArtifactBundle(root: str | Path, bundle: ArtifactBundle) -> Path

Publish a new manifest-last bundle exclusively and roll back only created files.

Source code in installed/flayer/profiles/artifacts.py
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
def WriteArtifactBundle(root: str | Path, bundle: ArtifactBundle) -> Path:
    """Publish a new manifest-last bundle exclusively and roll back only created files."""

    if not isinstance(bundle, ArtifactBundle):
        raise ArtifactError("bundle must be an ArtifactBundle")

    root_path = Path(root)
    bundle_name = f"{bundle.kind}-{bundle.name}"
    created: list[tuple[str, _FileReceipt]] = []
    directory: int | None = None

    try:
        with _Directory(root_path, create=True) as parent:
            with _BundleLock(parent, bundle_name):
                try:
                    os.mkdir(bundle_name, 0o700, dir_fd=parent)
                    directory = os.open(bundle_name, os.O_RDONLY | getattr(os, "O_DIRECTORY", 0) | getattr(os, "O_NOFOLLOW", 0), dir_fd=parent)

                    for artifact in bundle.files:
                        _AssertDirectory(parent, bundle_name, directory)
                        receipt = _WriteFile(directory, artifact.name, artifact.content)
                        created.append((artifact.name, receipt))

                    for name, receipt in created:
                        _AssertFile(directory, name, receipt)

                    _AssertDirectory(parent, bundle_name, directory)
                    receipt = _WriteFile(directory, _MANIFEST_NAME, _Manifest(bundle))
                    created.append((_MANIFEST_NAME, receipt))
                    os.fsync(directory)
                    os.fsync(parent)

                except BaseException:
                    if directory is not None:
                        _AssertDirectory(parent, bundle_name, directory)

                        for name, receipt in reversed(created):
                            _UnlinkFile(directory, name, receipt)

                        _AssertDirectory(parent, bundle_name, directory)
                        os.rmdir(bundle_name, dir_fd=parent)

                    raise

                finally:
                    if directory is not None:
                        os.close(directory)

    except (OSError, ValueError, RecursionError):
        raise ArtifactError("Unable to publish artifact bundle; existing bundles are never replaced") from None

    return root_path / bundle_name

BuildServerBundle

BuildServerBundle(profile: GatewayProfile) -> ArtifactBundle

Build initialization bytes without resolving secrets, uploading, or claiming readiness.

Source code in installed/flayer/profiles/gateway.py
557
558
559
560
561
562
563
564
565
def BuildServerBundle(profile: GatewayProfile) -> ArtifactBundle:
    """Build initialization bytes without resolving secrets, uploading, or claiming readiness."""

    CompileGateway(profile)

    return ArtifactBundle(
        identity=profile.identity, kind="server", name=profile.identity.stack,
        files=(Artifact("server-cloud-init.json", _CloudInit(profile), sensitive=False),),
    )

BuildSshDeviceBundle

BuildSshDeviceBundle(profile: GatewayProfile, device_id: str, *, endpoint: str, identity_file: str | Path, known_hosts_file: str | Path, local_ports: tuple[int, ...]) -> ArtifactBundle

Generate usable local TCP forwards with caller-provided key and trusted-host references.

Source code in installed/flayer/profiles/gateway.py
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
def BuildSshDeviceBundle(
    profile: GatewayProfile, device_id: str, *, endpoint: str,
    identity_file: str | Path, known_hosts_file: str | Path, local_ports: tuple[int, ...],
) -> ArtifactBundle:
    """Generate usable local TCP forwards with caller-provided key and trusted-host references."""

    CompileGateway(profile)
    ValidateName(device_id, "device_id")
    _Host(endpoint)
    key_path = _SshPath(identity_file)
    trust_path = _SshPath(known_hosts_file)
    device = next((item for item in profile.transport.devices if item.device_id == device_id), None)

    if device is None:
        raise GatewayError("Device must be declared in the compiled gateway transport")

    if not isinstance(local_ports, tuple) or len(local_ports) != len(profile.transport.targets):
        raise GatewayError("local_ports must contain one immutable port for each declared target")

    for port in local_ports:
        _Integer(port, 1024, 65535, "local port")

    if len(set(local_ports)) != len(local_ports):
        raise GatewayError("Local forward ports must be unique")

    lines = [
        f"Host {device_id}", f"    HostName {endpoint}", f"    Port {profile.ssh_port}",
        f"    User {profile.transport.username}", f'    IdentityFile "{key_path}"',
        f'    UserKnownHostsFile "{trust_path}"', "    GlobalKnownHostsFile none",
        "    StrictHostKeyChecking yes", "    VerifyHostKeyDNS no", "    UpdateHostKeys no",
        "    KnownHostsCommand none", "    IdentitiesOnly yes", "    IdentityAgent none",
        "    PreferredAuthentications publickey", "    PasswordAuthentication no",
        "    KbdInteractiveAuthentication no", "    ForwardAgent no", "    ForwardX11 no",
        "    RequestTTY no", "    SessionType none", "    ProxyCommand none", "    ProxyJump none",
        "    PermitLocalCommand no", "    CanonicalizeHostname no", "    ExitOnForwardFailure yes",
        "    ConnectTimeout 10", "    ConnectionAttempts 1", "    ServerAliveInterval 15",
        "    ServerAliveCountMax 3",
    ]

    for port, target in zip(local_ports, profile.transport.targets, strict=True):
        lines.append(f"    LocalForward 127.0.0.1:{port} {target.host}:{target.port}")

    descriptor = {
        "schema_version": 1, "device_id": device_id, "transport": "ssh-local-forward",
        "status": "prepared-unverified", "identity": {
            key: getattr(profile.identity, key) for key in sorted(_IDENTITY_FIELDS)
        },
        "public_key_sha256": hashlib.sha256(device.public_key.encode("ascii")).hexdigest(),
    }

    return ArtifactBundle(profile.identity, "device", device_id, (
        Artifact("ssh-client.conf", ("\n".join(lines) + "\n").encode("utf-8"), sensitive=False),
        Artifact("device-info.json", (json.dumps(descriptor, sort_keys=True) + "\n").encode("utf-8"), sensitive=False),
    ))

CompileGateway

CompileGateway(profile: GatewayProfile) -> GatewayPlan

Compile validated profile intent without performing filesystem or provider operations.

Source code in installed/flayer/profiles/gateway.py
435
436
437
438
439
440
441
442
443
def CompileGateway(profile: GatewayProfile) -> GatewayPlan:
    """Compile validated profile intent without performing filesystem or provider operations."""

    plan = GatewayPlan(profile)

    for logical_id in ("network", "subnet", "firewall", "address", "boot-disk", "instance"):
        ValidateName(profile.identity.stack + "-" + logical_id, "resource name")

    return plan

LoadGatewayProfile

LoadGatewayProfile(path: str | Path) -> GatewayProfile

Read a bounded explicit profile without consulting credentials or environment defaults.

Source code in installed/flayer/profiles/gateway.py
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
def LoadGatewayProfile(path: str | Path) -> GatewayProfile:
    """Read a bounded explicit profile without consulting credentials or environment defaults."""

    try:
        with Path(path).open("rb") as stream:
            raw = stream.read(MAX_PROFILE_BYTES + 1)

        if len(raw) > MAX_PROFILE_BYTES:
            raise GatewayError("Gateway profile exceeds its bounded input size")

        data = tomllib.loads(raw.decode("utf-8"))

    except (OSError, ValueError, RecursionError):
        raise GatewayError("Unable to read a valid bounded gateway profile") from None

    return ParseGatewayProfile(data)

ParseGatewayProfile

ParseGatewayProfile(data: Mapping[str, object]) -> GatewayProfile

Reject unknown settings, secret fields, and unversioned profile tables.

Source code in installed/flayer/profiles/gateway.py
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
def ParseGatewayProfile(data: Mapping[str, object]) -> GatewayProfile:
    """Reject unknown settings, secret fields, and unversioned profile tables."""

    try:
        ValidateFields(
            data, frozenset({"schema_version", "profile", "guest_contract", "identity", "gateway", "transport"}),
            frozenset({"service_ports"}), "gateway profile",
        )
        ValidateSchemaVersion(data["schema_version"])

        if data["profile"] != PROFILE_NAME or data["guest_contract"] != "ubuntu-24.04-cloud-init":
            raise GatewayError("profile and guest_contract must select the supported server foundation")

        gateway = RequireTable(data["gateway"], "gateway")
        ValidateFields(gateway, _GATEWAY_FIELDS, _OPTIONAL_GATEWAY_FIELDS, "gateway")
        raw_cidrs = gateway["management_cidrs"]
        raw_ports = data.get("service_ports", [])

        if not isinstance(raw_cidrs, list) or not isinstance(raw_ports, list):
            raise GatewayError("management_cidrs and service_ports must be arrays")

        service_ports: list[GatewayPort] = []

        for raw_port in raw_ports:
            port = RequireTable(raw_port, "service port")
            ValidateFields(port, frozenset({"protocol", "port", "cidrs"}), frozenset(), "service port")

            if not isinstance(port["protocol"], str) or not isinstance(port["cidrs"], list):
                raise GatewayError("service port requires a protocol and CIDR array")

            service_ports.append(GatewayPort(
                protocol=port["protocol"],
                port=_Integer(port["port"], 1, 65535, "service port"),
                cidrs=tuple(port["cidrs"]),
            ))

        return GatewayProfile(
            identity=ParseIdentity(data["identity"]),
            zone_id=ValidateIdentifier(gateway["zone_id"], "zone_id"),
            image_id=ValidateIdentifier(gateway["image_id"], "image_id"),
            subnet_cidr=_Cidr(gateway["subnet_cidr"], 16, "subnet_cidr"),
            ssh_public_key=_PublicKey(gateway["ssh_public_key"]),
            management_cidrs=tuple(raw_cidrs),
            transport=_ParseTransport(data["transport"]),
            service_ports=tuple(service_ports),
            ssh_username=_Username(gateway.get("ssh_username", "gateway-admin")),
            ssh_port=_Integer(gateway.get("ssh_port", 22), 1, 65535, "ssh_port"),
            cores=_Integer(gateway.get("cores", 2), 2, 32, "cores"),
            memory_gib=_Integer(gateway.get("memory_gib", 2), 1, 64, "memory_gib"),
            boot_disk_gib=_Integer(gateway.get("boot_disk_gib", 20), 10, 256, "boot_disk_gib"),
        )

    except ContractError as error:
        raise GatewayError(str(error)) from None

RenderPlan

RenderPlan(plan: GatewayPlan, *, user_data_file: str | Path) -> str

Bind exact generated bytes to a lifecycle TOML plan without importing future orchestration.

Source code in installed/flayer/profiles/gateway.py
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
def RenderPlan(plan: GatewayPlan, *, user_data_file: str | Path) -> str:
    """Bind exact generated bytes to a lifecycle TOML plan without importing future orchestration."""

    if not isinstance(plan, GatewayPlan):
        raise GatewayError("plan must be a GatewayPlan")

    profile = plan.profile
    artifact_path = Path(user_data_file)
    expected = _CloudInit(profile)

    try:
        actual = ReadOwnedArtifactFile(
            artifact_path, profile.identity, kind="server", name=profile.identity.stack,
        )

    except ContractError:
        raise GatewayError("Prepared server artifact ownership or integrity is invalid") from None

    if actual != expected:
        raise GatewayError("Initialization artifact does not match the compiled gateway profile")

    def Quote(value: str) -> str:
        """Keep Unicode scalar paths valid TOML instead of emitting surrogate escapes."""

        return json.dumps(value, ensure_ascii=False)

    lines = ["schema_version = 1", f"profile = {Quote(PROFILE_NAME)}", "", "[identity]"]

    for key in sorted(_IDENTITY_FIELDS):
        lines.append(f"{key} = {Quote(getattr(profile.identity, key))}")

    def AddResource(
        logical_id: str, kind: str, dependencies: tuple[str, ...], properties: Mapping[str, object],
    ) -> None:
        """Append deterministic resource intent with no observed provider identifiers."""

        lines.extend([
            "", "[[resources]]", f"logical_id = {Quote(logical_id)}", f"kind = {Quote(kind)}",
            f"name = {Quote(ValidateName(profile.identity.stack + '-' + logical_id, 'resource name'))}",
            "dependencies = [" + ", ".join(Quote(item) for item in dependencies) + "]",
            "[resources.parameters]",
        ])

        for key, value in properties.items():
            if isinstance(value, str):
                rendered = Quote(value)

            elif type(value) is int:
                rendered = str(value)

            else:
                raise GatewayError("Resource properties must be explicit scalar values")

            lines.append(f"{key} = {rendered}")

    AddResource("network", "network", (), {})
    AddResource("subnet", "subnet", ("network",), {
        "zone_id": profile.zone_id, "ipv4_cidr": profile.subnet_cidr,
        "network_dependency": "network",
    })
    AddResource("firewall", "security-group", ("network",), {"network_dependency": "network"})
    rules = [("tcp", profile.ssh_port, cidr) for cidr in _SshCidrs(profile)]
    rules.extend((service.protocol, service.port, cidr) for service in profile.service_ports for cidr in service.cidrs)

    for protocol, port, cidr in rules:
        lines.extend([
            "[[resources.parameters.rules]]", 'direction = "ingress"', f"protocol = {Quote(protocol)}",
            f"from_port = {port}", f"to_port = {port}", f"cidr = {Quote(cidr)}",
        ])

    lines.extend([
        "[[resources.parameters.rules]]", 'direction = "egress"', 'protocol = "any"',
        'cidr = "0.0.0.0/0"',
    ])
    AddResource("address", "address", (), {"zone_id": profile.zone_id})
    AddResource("boot-disk", "disk", (), {
        "zone_id": profile.zone_id, "image_id": profile.image_id, "size_gib": profile.boot_disk_gib,
    })
    AddResource("instance", "instance", ("subnet", "firewall", "address", "boot-disk"), {
        "zone_id": profile.zone_id, "cores": profile.cores,
        "memory_gib": profile.memory_gib, "boot_disk_dependency": "boot-disk",
        "subnet_dependency": "subnet", "security_group_dependency": "firewall",
        "address_dependency": "address", "ssh_public_key": profile.ssh_public_key,
        "ssh_username": profile.ssh_username, "user_data_file": str(artifact_path.absolute()),
        "user_data_sha256": hashlib.sha256(expected).hexdigest(),
    })

    return "\n".join(lines) + "\n"