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