Documentation
¶
Overview ¶
Package catalog is the shared price/availability catalog for providers whose APIs do not expose a rate card (Modal, and most NeoClouds). It follows SkyPilot's community-catalog pattern: prices live in checked-in, git-tracked CSV files (pkg/provider/catalog/data/<provider>.csv) rather than hardcoded in Go, so a price change is a reviewable data diff.
Two-tier load, override-first:
- If an override directory is set (NEBULA_CATALOG_DIR, or LoadFrom(dir)), CSVs there win. This is how the deployed controller consumes a ConfigMap: the CSVs are rendered into a ConfigMap and mounted, so ops can edit prices live (kubectl edit configmap) without rebuilding the image.
- Otherwise the CSVs embedded at build time are used as the default, so the binary always has a working catalog even with nothing mounted.
A provider's Offerings() becomes a lookup into this catalog instead of a hardcoded table.
Index ¶
Constants ¶
const OverrideDirEnv = "NEBULA_CATALOG_DIR"
OverrideDirEnv is the env var pointing at a directory of provider CSVs that override the embedded defaults. Set by the manager Deployment to the ConfigMap mount path.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Base ¶
type Base struct {
// ProviderName is this provider's stable identifier (e.g. "modal"), used both
// as Name() and as the key into the catalog.
ProviderName string
// Catalog is the shared price/availability lookup.
Catalog Lookup
}
Base supplies the parts of provider.Provider that are identical for every adapter whose price/availability comes from a catalog: Name, Offerings, and a default identity MapAccelerator. Adapters embed it so those three methods are not re-implemented per provider:
type Provider struct {
catalog.Base
client Client
}
Name and Offerings are fully generic. MapAccelerator is generic only while a provider names its accelerators like Nebula's canonical names (Modal does); one whose identifiers diverge overrides just that method and still reuses the rest.
Lifecycle, Capabilities and ClassifyProvisionError are genuinely provider-specific and are not provided here.
func (Base) ExpandRegions ¶
ExpandRegions passes the declared regions through unchanged: one candidate each, tokens used verbatim as region names. Right for a provider whose own vocabulary already spans both levels the pool speaks AND whose provision reports capacity failures synchronously, so walking candidates actually buys a retry in the next region. nil stays nil, which every adapter reads as "unconstrained".
Both halves have real overriders, in opposite directions: AWS expands a group token into many candidates ("us" is not a callable region), while Modal collapses everything into ONE candidate because its create cannot fail over. Check which a new provider resembles before inheriting this.
func (Base) MapAccelerator ¶
func (b Base) MapAccelerator(canonical string, count int32) (providerAcceleratorIDs []string, ok bool)
MapAccelerator translates a canonical accelerator request (type + count) into this provider's own ids, using the catalog as the mapping table: matching rows contribute their AcceleratorIDs in catalog order — PRIMARY first, then interchangeable alternates, deduped. A blank AcceleratorID falls back to the canonical name, so an identity-mapped provider needs no per-name data. Since the mapping is all in the CSV, a provider whose ids diverge just fills in accelerator_id/gpu_count instead of overriding this.
Count matching honours both catalog shapes (see Offering.GPUCount): a provider that bakes the count in (AWS: T4x1=g4dn.xlarge, T4x8=g4dn.metal) emits one row per count, which is what keeps (L4, 1) and (L4, 8) on DISTINCT primaries so one's block cannot exclude the other. A provider taking count as a parameter (Modal) leaves GPUCount 0, and a 0 row matches any count.
Dedup collapses the per-capacity-type/per-region duplicates (an instance type is the same object whether the row prices Spot or OnDemand). ids[0] is what failover blocks on; alternates broaden a launch but never the blocklist. ok=false when no row matches.
Availability gates the mapping: a row with Available false contributes no id, so a (type, count) with no available row maps to ok=false. This is the one seam placement, AWS and Modal all consult, so flipping a CSV row off removes it from scheduling everywhere without touching Go.
func (Base) Offerings ¶
Offerings returns this provider's rows from the catalog. The error is always nil today (the catalog is in-memory); the signature matches provider.Provider so an adapter that later combines the static catalog with a live availability probe can return a real error without a signature change.
type Catalog ¶
type Catalog struct {
// contains filtered or unexported fields
}
Catalog holds parsed offerings keyed by provider name.
func Load ¶
Load builds a Catalog, preferring CSVs in the override dir named by NEBULA_CATALOG_DIR when that env var is set and the dir exists, otherwise falling back to the embedded defaults.
func LoadFrom ¶
LoadFrom builds a Catalog from CSV files in dir (used for the ConfigMap mount and in tests). Each file must be named "<provider>.csv".
type Lookup ¶
type Lookup interface {
// Offerings returns providerName's rows, or nil if the provider has no
// catalog entry. Implementations return a copy the caller may safely annotate.
Offerings(providerName string) []provider.Offering
}
Lookup is the price/availability seam a provider adapter depends on: given a provider name it returns that provider's offering rows. The concrete *Catalog satisfies it; tests inject a trivial fake so an adapter can be unit-tested without embedding CSVs. It lives in this package (not in provider) so all catalog-shaped types share one home and there is no provider.Catalog / catalog.Catalog name clash.