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