k8ts
Version:
Powerful framework for building Kubernetes manifests in TypeScript.
92 lines (80 loc) • 3.13 kB
text/typescript
import { OriginNode, Resource_Node, type Node, type Resource_Top } from "@k8ts/instruments"
import Emittery from "emittery"
import { MakeError } from "../../error"
export class ResourceLoader extends Emittery<ResourceLoaderEventsTable> {
constructor(private readonly _options: ResourceLoaderOptions) {
super()
}
private _checkNames(resources: Resource_Node[]) {
let names = new Map<string, Resource_Node>()
const nameRegexp = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/
for (const resource of resources) {
const name = [resource.kind.name, resource.namespace, resource.name]
.filter(Boolean)
.join("/")
const existing = names.get(name)
if (existing) {
throw new MakeError(
`Duplicate resource designation ${name}. Existing: ${existing.format("source")}, new: ${resource.format("source")}`
)
}
if (!nameRegexp.test(resource.name)) {
throw new MakeError(
`Invalid resource name ${resource.name}. Must match ${nameRegexp}`
)
}
names.set(name, resource)
}
}
async load(input: OriginNode) {
// TODO: Handle ORIGINS that are referenced but not passed to the runner
let resources = [] as Resource_Node[]
const addResource = async (res: Node) => {
if (!(res instanceof Resource_Node)) {
throw new Error(`Expected ResourceNode, got ${res.constructor.name}`)
}
if (resources.some(r => r.equals(res))) {
return
}
if (!res.isRoot) {
return
}
const origin = res.origin
if (!origin.isChildOf(input)) {
return
}
const event = {
isExported: res.meta!.has(`#k8ts.org/is-exported`),
resource: res
} as const
await this.emit("load", event)
const resourceOriginMetas = res.origin.inheritedMeta
res.meta!.add(resourceOriginMetas)
origin.entity["__emit__"]("resource/loaded", {
origin: origin.entity,
resource: res.entity as Resource_Top
})
resources.push(res)
}
for (const resource of input.resources) {
await addResource(resource.node)
}
// Some resources might appear as dependencies to sub-resources that
// haven't loaded. We can acquire them by getting all the needed resources
for (const resource of resources) {
for (const relation of resource.recursiveRelationsSubtree) {
await addResource(relation.needed)
}
}
this._checkNames(resources)
return [...resources]
}
}
export interface ResourceLoadedEvent {
isExported: boolean
resource: Resource_Node
}
export interface ResourceLoaderEventsTable {
load: ResourceLoadedEvent
}
export interface ResourceLoaderOptions {}