Skip to main content

IngressEdge

Struct IngressEdge 

Source
pub struct IngressEdge {
    pub provider: IngressProvider,
    pub machines: Vec<String>,
    pub slots: Vec<String>,
    pub hostnames: Vec<String>,
    pub tunnel_id: Option<String>,
    pub provider_id: Option<String>,
    pub image: Option<String>,
    pub auth: Option<PasswayAuth>,
    pub via: Option<IngressVia>,
    pub tunnel_door: Option<TunnelDoor>,
}
Expand description

One declared edge: a front door, the slots it fronts, and the nodes it is placed on (W305 F2).

A mirror declares a list of these, which is what lets one service mix front doors — cloudflare for the public web tier, passway for an internal or high-throughput one. Before this, MirrorConfig::ingress was a single IngressProvider, so a mirror could swap front doors but never mix them.

[[ingress]]
provider = "passway"
machines = ["us-east-001", "us-south-001"]
slots    = ["bundle"]

[[ingress]]
provider  = "cloudflare-tunnel"
hostnames = ["issues.yah.dev"]

The per-node appliance is derived from this, never declared beside it. An edge does invoke a cloudflared or passway process on a box, but that is a consequence of the service’s declaration: collate_front_doors walks every service and derives what each node must run. Declaring it node-side too is what produces two sources of truth for one fact.

Fields§

§provider: IngressProvider

Which front door this edge is. IngressProvider::None is rejected at plan time — an edge that fronts with nothing is always a typo, never an intent (write no edge instead).

§machines: Vec<String>

Nodes this front door is placed on — independent of where the fronted workload runs (R330-F37).

Empty falls back to the fronted slot’s own machine / machines, which is the co-located shape every mirror had before front-door placement was expressible. Listing several is what lets the ingress tier and the service tier scale independently: N front doors over ONE deployment, one rendered copy, so no cache coherence to settle.

§slots: Vec<String>

Provider slot roles this edge fronts ("bundle", "compute", …).

One of the two selectors. With a single edge both may be empty, meaning “every fronted slot” — the legacy shape. With several edges a selector is mandatory on each, and the partition must be total and disjoint: a slot claimed by no edge, or by two, is an error naming it. An implicit catch-all across mixed front doors would silently publish a service through the wrong one.

§hostnames: Vec<String>

Public hostnames this edge fronts — the other selector, for partitioning by what the world dials rather than by which slot serves it.

§tunnel_id: Option<String>

Cloudflare Tunnel id this edge publishes through, overriding the fronting machine’s MachineConfig::cloudflared.

This is W267 Gap 3’s real fix, and it is the service side of it: a node can join two cohorts’ orange networks, and since §Granularity argues the tunnel credential is the isolation boundary, which cohort a given service fronts through is a property of the service, not of the box. MachineConfig.cloudflared stays as the per-node default (one tunnel is the common case, and the credential does live on the node), but it is no longer the only way to say it — so the node never has to enumerate cohorts.

§provider_id: Option<String>

Infra provider id whose credentials this edge’s front door authenticates with — use = "cloudflare", resolved through .yah/infra/providers/<id>.toml exactly as a slot’s use is.

Same split as tunnel_id, one field over: whose Cloudflare account holds the tunnel is a property of the front door, not of the box that runs the compute. Without this the account was read off the fronted slot’s own use, which conflates two unrelated facts — and is unwritable for a slot whose compute provider is kind = "static" (a borrowed bare box: placement only, no credentials). Such a mirror had no way to name a Cloudflare account at all, short of writing use = "cloudflare" on the compute slot and lying about what runs it (R845).

None falls back to the fronted slot’s use, which is what every mirror written before this field meant.

§image: Option<String>

Digest-pinned image reference for this edge’s front-door appliance, e.g. localhost/passway:tag@sha256:<hex> (R870-F16).

None is the state of every mirror on disk today: the passway arm of yah cloud apply cannot deploy an appliance the mirror doesn’t name an image for, so it renders the manual yah cloud ingress deploy … --image <passway-ref> step instead of running it. Declaring this field is what makes the arm self-sufficient, matching the CloudflareTunnel arm’s real-API-call shape rather than only printing for an operator to copy by hand.

§auth: Option<PasswayAuth>

Cheers bearer-auth for this edge’s door, spelled as an [ingress.auth] table under the [[ingress]] entry (R870-F26).

[[ingress]]
provider = "passway"
image    = "localhost/passway:v1@sha256:…"

[ingress.auth]
key_secret       = "cheers/yah-camp/verify"
kid              = "YOHV4Riq-g8fX4uYl8rTjQ"
iss              = "yah-camp"
aud              = "analytics.yah.dev"
require_prefixes = ["/"]

This is what makes an apply-driven push FAITHFUL rather than merely blocked. Before it, yah cloud apply’s Passway arm rebuilt the door’s spec with auth: None because a mirror had no way to say otherwise, and /workloads/deploy is a full replace — so pushing at a door someone had deployed with --auth-key-secret … took its auth away and brought it back anonymous (R870-B24). That strip is guarded by a read-back in push_passway_ingress, and the guard STAYS: it covers a door that acquired auth in a way no mirror can see. This field is what lets the common case sail past that guard by carrying the auth instead of losing it — the guard early-returns on any push that carries auth of its own.

PasswayAuth verbatim, not a config-side copy of its five fields: all five are required by Deserialize, so a half-written table is refused by serde naming the missing field, and the renderer that emits the PASSWAY_AUTH_* variables reads the very same struct.

Only meaningful on a provider = "passway" edge — a cloudflare-tunnel edge carrying one is refused by MirrorConfig::ingress_edges rather than silently ignored, since ignoring it yields exactly the believed-protected-but-public door this vocabulary exists to prevent.

§via: Option<IngressVia>

Stack this edge in front of another front door on the same node instead of dialing the workload (R910) — see IngressVia.

[[ingress]]
provider  = "passway"
machines  = ["us-west-011"]
hostnames = ["api-staging.noisetable.com"]

[[ingress]]
provider  = "cloudflare-tunnel"
via       = "passway"
use       = "cloudflare-tunnel-staging"
machines  = ["us-west-011"]
hostnames = ["api-staging.noisetable.com"]

Only a cloudflare-tunnel edge may carry it, and the mirror must also declare the edge it names, claiming the same hostnames on the same machines — the tunnel dials its own node’s loopback demux, so a pair split across nodes routes to nothing. Refused otherwise by plan_ingress, naming both edges.

None is every mirror written before R910.

§tunnel_door: Option<TunnelDoor>

The door behind a tunnel, spelled [ingress.tunnel_door] on the passway edge a via = "passway" tunnel stacks in front of (R910-F2).

[ingress.tunnel_door]
contact_email = "ops@example.com"
zone_id       = "<cloudflare zone id>"
token_secret  = "example/staging/cf-dns-token"
ports         = { "staging.example.com" = 8445 }

Required exactly when the edge is derived behind a tunnel, refused otherwise — see partition. yah cloud apply turns it into one scoped enrollment per hostname, which the tunnel’s machines route, arm and issue from; see TunnelDoor.

Implementations§

Source§

impl IngressEdge

Source

pub fn all_slots(provider: IngressProvider, machines: Vec<String>) -> Self

An edge with no selector — fronts every fronted slot, legal only when it is the mirror’s only edge.

Source

pub fn validate_via(&self) -> Result<()>

Refuse via on an edge that cannot stack (R910). A passway edge terminates the connection itself, so via there would be ignored — and an ignored via is a mirror that reads as tunnel-fronted while publishing the door’s own address.

Source

pub fn validate_auth(&self) -> Result<()>

Refuse an [ingress.auth] table that cannot produce a protected door (R870-F26). Called from MirrorConfig::ingress_edges, so every reader of a mirror — plan, collate, yah cloud validate, apply — gets it.

Two failures, and they fail in opposite directions, which is why both are here rather than left to the deploy:

  • Auth on a non-passway edge. Nothing downstream would read it, so the operator gets a door they believe is protected and is not. Only passway renders PASSWAY_AUTH_*; a cloudflare-tunnel edge publishes through Cloudflare Access instead and has no place to put these.
  • A present-but-empty field. Deserialize already refuses a missing one by name; PasswayAuth::validate covers the rest, and is the same implementation yah cloud ingress deploy runs on its flags — so the two doors cannot diverge on what counts as configured.
Source

pub fn validate_tunnel_door(&self) -> Result<()>

Refuse an [ingress.tunnel_door] that cannot produce a working door (R910-F2). Whether the edge is actually behind a tunnel is a property of the pair, so partition checks that half.

Source

pub fn has_selector(&self) -> bool

true when this edge names which slots/hostnames it fronts.

Source

pub fn claims(&self, slot: &str, hostname: &str) -> bool

Does this edge claim the rule derived from slot publishing hostname?

A selectorless edge claims everything; that is checked to be unambiguous (one edge only) before this is consulted.

Source

pub fn label(&self) -> String

Human-readable identity for an error message — the provider plus whichever selector was written.

Trait Implementations§

Source§

impl Clone for IngressEdge

Source§

fn clone(&self) -> IngressEdge

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for IngressEdge

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for IngressEdge

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Eq for IngressEdge

Source§

impl PartialEq for IngressEdge

Source§

fn eq(&self, other: &IngressEdge) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl Serialize for IngressEdge

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for IngressEdge

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Allocation for T
where T: RefUnwindSafe + Send + Sync,

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Convert Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Source§

fn as_any(&self) -> &(dyn Any + 'static)

Convert &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
Source§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Converts Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Converts Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Source§

fn as_any(&self) -> &(dyn Any + 'static)

Converts &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
Source§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Converts &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> DowncastSend for T
where T: Any + Send,

Source§

fn into_any_send(self: Box<T>) -> Box<dyn Any + Send>

Converts Box<Trait> (where Trait: DowncastSend) to Box<dyn Any + Send>, which can then be downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

Source§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Sync + Send> ⓘ

Convert Arc<Trait> (where Trait: Downcast) to Arc<Any>. Arc<Any> can then be further downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

Source§

fn into_any_sync(self: Box<T>) -> Box<dyn Any + Sync + Send>

Converts Box<Trait> (where Trait: DowncastSync) to Box<dyn Any + Send + Sync>, which can then be downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Sync + Send> ⓘ

Converts Arc<Trait> (where Trait: DowncastSync) to Arc<Any>, which can then be downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
Source§

impl<K, Q> Equivalent<Q> for K
where K: Borrow<Q> + ?Sized, Q: Eq + ?Sized,

Source§

fn equivalent(&self, key: &Q) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<T> Fruit for T
where T: Send + Downcast,

Source§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> Serialize for T
where T: Serialize + ?Sized,

Source§

fn erased_serialize(&self, serializer: &mut dyn Serializer) -> Result<(), Error>

Source§

fn do_erased_serialize( &self, serializer: &mut dyn Serializer, ) -> Result<(), ErrorImpl>

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more