-
Notifications
You must be signed in to change notification settings - Fork 290
/
crd.ts
129 lines (106 loc) · 3.73 KB
/
crd.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import { TypeGenerator } from './type-generator';
import { ImportBase } from './base';
import { CodeMaker } from 'codemaker';
import { httpsGet } from '../util';
import * as yaml from 'yaml';
import * as fs from 'fs-extra';
import * as path from 'path';
export interface CustomResourceApiObject {
apiVersion?: string;
kind?: string;
metadata?: {
name?: string;
};
spec?: {
group: string;
names: {
kind: string;
};
versions?: Array<{ name: string; schema?: { openAPIV3Schema?: any } }>;
validation?: { openAPIV3Schema?: any };
};
}
export class CustomResourceDefinition {
private readonly schema: any;
private readonly group: string;
private readonly version: string;
private readonly kind: string;
private readonly fqn: string;
constructor(manifest: CustomResourceApiObject) {
assert(manifest.apiVersion === 'apiextensions.k8s.io/v1beta1', '"apiVersion" must be "apiextensions.k8s.io/v1beta1"');
assert(manifest.kind === 'CustomResourceDefinition', '"kind" must be "CustomResourceDefinition"');
const spec = manifest.spec;
if (!spec) {
throw new Error(`manifest does not have a "spec" attribute`)
}
const version = (spec.versions ?? [])[0];
if (!version) {
throw new Error(`unable to determine CRD version`);
}
if (!manifest.metadata?.name) {
throw new Error(`"metadata.name" is required`);
}
const schema = version.schema?.openAPIV3Schema ?? spec.validation?.openAPIV3Schema;
if (!schema) {
throw new Error(`unable to determine schema for custom resource`);
}
this.schema = schema;
this.group = spec.group;
this.version = version.name;
this.kind = spec.names.kind;
this.fqn = this.kind;
}
public get moduleName() {
return this.kind.toLocaleLowerCase();
}
public async generateTypeScript(code: CodeMaker) {
const types = new TypeGenerator();
types.emitConstruct({
group: this.group,
version: this.version,
kind: this.kind,
fqn: this.fqn,
schema: this.schema
});
code.line(`// generated by cdk8s`);
code.line(`import { ApiObject } from 'cdk8s';`);
code.line(`import { Construct } from 'constructs';`);
code.line();
types.generate(code);
}
}
export class ImportCustomResourceDefinition extends ImportBase {
public static async match(source: string): Promise<undefined | CustomResourceApiObject[]> {
let manifest;
if (source.startsWith('https://')) {
manifest = await httpsGet(source);
} else if (path.extname(source) === '.yaml' || path.extname(source) === '.yml' || path.extname(source) === '.json') {
if (!(await fs.pathExists(source))) {
throw new Error(`can't find file ${source}`);
}
manifest = await fs.readFile(source, 'utf-8');
}
if (!manifest) {
return undefined;
}
return yaml.parseAllDocuments(manifest)
.map((doc: yaml.ast.Document) => doc.toJSON())
.filter((doc) => (doc as CustomResourceApiObject).kind === 'CustomResourceDefinition');
}
private readonly customResourceDefinitions: CustomResourceDefinition[] = [];
constructor(manifest: CustomResourceApiObject[]) {
super();
this.customResourceDefinitions = manifest?.map(obj => new CustomResourceDefinition(obj));
}
public get moduleNames() {
return this.customResourceDefinitions.map(crd => crd.moduleName);
}
protected async generateTypeScript(code: CodeMaker, moduleName: string) {
this.customResourceDefinitions.filter(crd => moduleName === crd.moduleName).map(crd => crd.generateTypeScript(code));
}
}
function assert(condition: boolean, message: string) {
if (!condition) {
throw new Error(`invalid CustomResourceDefinition manifest: ${message}`);
}
}