1 | import * as fs from 'fs-extra-plus';
|
2 | import * as Path from 'path';
|
3 | import { BaseObj } from './base';
|
4 | import { Block } from './block';
|
5 | import { _getImageName } from './docker';
|
6 | import { callHook } from './hook';
|
7 | import { getCurrentContext, setCurrentContext } from './k8s';
|
8 | import { render } from './renderer';
|
9 | import { asNames } from './utils';
|
10 | import { loadVdevConfig } from './vdev-config';
|
11 |
|
12 |
|
13 |
|
14 |
|
15 | export type RealmType = 'local' | 'gcp' | 'aws' | 'azure';
|
16 | export interface Realm extends BaseObj {
|
17 |
|
18 | name: string;
|
19 |
|
20 |
|
21 | context: string | null;
|
22 |
|
23 | type: RealmType;
|
24 |
|
25 |
|
26 | project?: string;
|
27 |
|
28 | |
29 |
|
30 |
|
31 |
|
32 | registry: string;
|
33 |
|
34 |
|
35 | defaultConfigurations?: string[];
|
36 |
|
37 | [key: string]: any;
|
38 | }
|
39 |
|
40 | export type RealmByName = { [name: string]: Realm };
|
41 |
|
42 | export type RealmChange = { profileChanged?: boolean, contextChanged?: boolean };
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 | export async function setRealm(realm: Realm) {
|
52 | const currentRealm = await getCurrentRealm(false);
|
53 | const change: RealmChange = {};
|
54 |
|
55 |
|
56 |
|
57 |
|
58 | const hookReturn = await callHook(realm, 'realm_set_begin', currentRealm);
|
59 |
|
60 | if (hookReturn != null) {
|
61 | change.profileChanged = true;
|
62 | }
|
63 |
|
64 | if (realm.context === null) {
|
65 | console.log(`INFO: realm ${realm.name} does not have a kubernetes context, skipping 'kubectl config use-context ...' (current kubectl context still active)`);
|
66 | } else if (!currentRealm || currentRealm.context !== realm.context) {
|
67 | change.contextChanged = true;
|
68 | await setCurrentContext(realm.context);
|
69 | }
|
70 |
|
71 |
|
72 | return change;
|
73 | }
|
74 |
|
75 |
|
76 |
|
77 |
|
78 | export async function getCurrentRealm(check = true) {
|
79 | const realms = await loadRealms();
|
80 | const context = await getCurrentContext();
|
81 |
|
82 | let realm;
|
83 |
|
84 | for (let realmName in realms) {
|
85 | let realmItem = realms[realmName];
|
86 | if (realmItem.context === context) {
|
87 | realm = realmItem;
|
88 | break;
|
89 | }
|
90 | }
|
91 |
|
92 | if (check && realm) {
|
93 | await callHook(realm, 'realm_check');
|
94 | }
|
95 |
|
96 |
|
97 | if (!realm) {
|
98 | realm = Object.values(realms).find(r => r.context === null);
|
99 | }
|
100 |
|
101 | return realm;
|
102 | }
|
103 |
|
104 |
|
105 |
|
106 |
|
107 |
|
108 |
|
109 |
|
110 |
|
111 |
|
112 | export async function renderRealmFile(realm: Realm, name: string): Promise<string> {
|
113 | const realmOutDir = getRealmOutDir(realm);
|
114 |
|
115 | const srcYamlFile = getKFile(realm, name);
|
116 | const srcYamlFileName = Path.parse(srcYamlFile).base;
|
117 | const srcYamlContent = await fs.readFile(srcYamlFile, 'utf8');
|
118 |
|
119 | const outYamlFile = Path.join(realmOutDir, srcYamlFileName);
|
120 |
|
121 |
|
122 | var data = realm;
|
123 | const outYamlContent = await render(srcYamlContent, data);
|
124 |
|
125 |
|
126 | await fs.ensureDir(realmOutDir);
|
127 | await fs.writeFile(outYamlFile, outYamlContent);
|
128 |
|
129 | return outYamlFile;
|
130 | }
|
131 |
|
132 |
|
133 |
|
134 | export function formatAsTable(realms: RealmByName, currentRealm?: Realm | null) {
|
135 | const txts = [];
|
136 | const header = ' ' + 'REALM'.padEnd(20) + 'TYPE'.padEnd(12) + 'PROJECT/PROFILE'.padEnd(20) + 'CONTEXT';
|
137 | txts.push(header);
|
138 |
|
139 | const currentRealmName = (currentRealm) ? currentRealm.name : null;
|
140 | const currentProject = (currentRealm) ? currentRealm.project : null;
|
141 | for (let realm of Object.values(realms)) {
|
142 | let row = (realm.name === currentRealmName) ? "* " : " ";
|
143 | row += realm.name.padEnd(20);
|
144 | row += realm.type.padEnd(12);
|
145 | let profile = (realm.type === 'gcp') ? realm.project : realm.profile;
|
146 | profile = (profile == null) ? '' : profile;
|
147 | row += profile.padEnd(20);
|
148 | row += (realm.context ? realm.context : 'NO CONTEXT FOUND');
|
149 | txts.push(row);
|
150 | }
|
151 | return txts.join('\n');
|
152 | }
|
153 |
|
154 |
|
155 |
|
156 |
|
157 | export type TemplateRendered = { name: string, path: string };
|
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 | export async function templatize(realm: Realm, resourceNames?: string | string[]): Promise<TemplateRendered[]> {
|
164 | const names = await getConfigurationNames(realm, resourceNames);
|
165 | const result: TemplateRendered[] = [];
|
166 |
|
167 | for (let name of names) {
|
168 | const path = await renderRealmFile(realm, name);
|
169 | result.push({ name, path });
|
170 | }
|
171 | return result;
|
172 | }
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 |
|
181 | export async function getConfigurationNames(realm: Realm, configurationNames?: string | string[]) {
|
182 |
|
183 |
|
184 |
|
185 | if (realm.context === null) {
|
186 | throw Error(`Realm '${realm.name}' does not have a Kubernetes context, cannot perform kubectly commands.`);
|
187 | }
|
188 |
|
189 | if (configurationNames) {
|
190 | return asNames(configurationNames);
|
191 | } else if (realm.defaultConfigurations) {
|
192 | return realm.defaultConfigurations;
|
193 | } else {
|
194 | return getAllConfigurationNames(realm);
|
195 | }
|
196 | }
|
197 |
|
198 |
|
199 |
|
200 |
|
201 |
|
202 |
|
203 |
|
204 | export function getRemoteImageName(block: Block, realm: Realm) {
|
205 | return _getImageName(block, realm.registry);
|
206 | }
|
207 |
|
208 |
|
209 | export function assertRealm(realm?: Realm): Realm {
|
210 | if (!realm) {
|
211 | throw new Error(`No realm found, do a 'npm run realm' to see the list of realm, and 'npm run realm realm_name' to set a realm`);
|
212 | }
|
213 | return realm;
|
214 | }
|
215 |
|
216 |
|
217 |
|
218 | export async function loadRealms(rootDir?: string): Promise<RealmByName> {
|
219 | const rawConfig = await loadVdevConfig(rootDir);
|
220 |
|
221 | const rawRealms: { [name: string]: any } = rawConfig.realms;
|
222 | const realms: RealmByName = {};
|
223 |
|
224 | const base = {
|
225 | system: rawConfig.system,
|
226 | k8sDir: rawConfig.k8sDir
|
227 | }
|
228 |
|
229 | let _common = {};
|
230 | if (rawRealms._common) {
|
231 | _common = rawRealms._common;
|
232 | delete rawRealms._common;
|
233 | }
|
234 |
|
235 |
|
236 | for (let name in rawRealms) {
|
237 | const rawRealm = rawRealms[name];
|
238 |
|
239 |
|
240 | const realm = { ...base, ..._common, ...rawRealm };
|
241 |
|
242 |
|
243 |
|
244 | realm.imageTag = rawConfig.imageTag;
|
245 |
|
246 |
|
247 | let type: RealmType = 'local';
|
248 | const context: undefined | string = realm.context;
|
249 | if (context) {
|
250 | if (context.startsWith('arn:aws')) {
|
251 | type = 'aws';
|
252 | realm.profile = (realm.profile != null) ? realm.profile : 'default';
|
253 | } else if (context.startsWith('gke')) {
|
254 | type = 'gcp';
|
255 | } else if (realm.registry && realm.registry.includes('azurecr')) {
|
256 | type = 'azure';
|
257 | }
|
258 | } else {
|
259 | realm.context = null;
|
260 | }
|
261 | realm.type = type;
|
262 |
|
263 |
|
264 | if (!realm.registry) {
|
265 | if (type === 'local' && realm.context) {
|
266 | realm.registry = 'localhost:5000/';
|
267 | } else if (type === 'gcp') {
|
268 | realm.registry = `gcr.io/${realm.project}/`;
|
269 | } else if (type === 'aws') {
|
270 | console.log(`WARNING - realm ${realm.name} of type 'aws' must have a registry property in the vdev.yaml`);
|
271 | }
|
272 | }
|
273 |
|
274 |
|
275 | realm.name = name;
|
276 |
|
277 |
|
278 |
|
279 | await callHook(realm, 'realm_init');
|
280 |
|
281 | realms[name] = realm;
|
282 | }
|
283 | return realms;
|
284 | }
|
285 |
|
286 |
|
287 |
|
288 |
|
289 |
|
290 | async function getAllConfigurationNames(realm: Realm): Promise<string[]> {
|
291 | const dir = getRealmSrcDir(realm);
|
292 | const yamlFiles = await fs.glob('*.yaml', dir);
|
293 |
|
294 |
|
295 | if (yamlFiles) {
|
296 | return yamlFiles.map((f: string) => { return Path.basename(f, '.yaml') });
|
297 | } else {
|
298 | return [];
|
299 | }
|
300 | }
|
301 |
|
302 | function getRealmOutDir(realm: Realm) {
|
303 | return Path.join(realm.k8sDir, '.out/', realm.name + '/');
|
304 | }
|
305 |
|
306 | function getRealmSrcDir(realm: Realm) {
|
307 | return Path.join(realm.k8sDir, realm.yamlDir);
|
308 | }
|
309 |
|
310 |
|
311 | function getKFile(realm: Realm, kName: string) {
|
312 | let k8sDir = getRealmSrcDir(realm);
|
313 | return Path.join(k8sDir, `${kName.trim()}.yaml`);
|
314 | }
|
315 |
|
316 | async function cleanRealmOutDir(realm: Realm) {
|
317 | await fs.saferRemove(getRealmOutDir(realm));
|
318 | }
|
319 |
|
320 |
|
321 |
|
322 |
|
323 |
|
324 |
|
325 |
|
326 |
|
327 |
|
328 |
|