k8ts
Version:
Powerful framework for building Kubernetes manifests in TypeScript.
105 lines (92 loc) • 3.63 kB
text/typescript
import { ForwardRef, ResourceNode } from "@k8ts/instruments"
import { seq } from "doddle"
import Emittery from "emittery"
import { Map, Set } from "immutable"
import { MakeError } from "../../error"
import type { File } from "../../file"
import { k8ts_namespace } from "./meta"
export class ResourceLoader extends Emittery<ResourceLoaderEventsTable> {
constructor(private readonly _options: ResourceLoaderOptions) {
super()
}
private _attachSourceAnnotations(loadEvent: ResourceLoadedEvent) {
const { resource } = loadEvent
resource.meta!.add(k8ts_namespace, {
"^declared-in": `(${resource.origin.root.name}) ${resource.origin.name}`
})
}
private _checkNames(resources: Set<ResourceNode>) {
let names = Map<string, ResourceNode>()
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 = names.set(name, resource)
}
}
async load(input: File.Input) {
// TODO: Handle ORIGINS that are referenced but not passed to the runner
const parentOrigin = input.__node__
const addResource = async (res: ResourceNode) => {
if (resources.has(res)) {
return
}
if (!res.isRoot) {
return
}
const origin = res.origin
if (!origin.isChildOf(parentOrigin)) {
return
}
const event = {
isExported: res.meta!.has(`#${k8ts_namespace}is-exported`),
resource: res
} as const
this._attachSourceAnnotations(event)
await this.emit("load", event)
resources = resources.add(res)
}
let resources = Set<ResourceNode>()
// We execute the main FILE iterable to load all the resources attached to the origin
seq(input).toArray().pull()
for (let res of input.__node__.resources) {
if (ForwardRef.is(res)) {
throw new MakeError(`Resource ${res} is a forward reference`)
}
await addResource(res)
}
// 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.toArray()) {
for (const relation of resource.recursiveRelationsSubtree) {
await addResource(relation.needed)
}
}
// The lazy sections might have attached more resources to the origin
for (const resource of parentOrigin.resources) {
await addResource(resource)
}
this._checkNames(resources)
return resources.toArray()
}
}
export interface ResourceLoadedEvent {
isExported: boolean
resource: ResourceNode
}
export interface ResourceLoaderEventsTable {
load: ResourceLoadedEvent
}
export interface ResourceLoaderOptions {}