1 | import { spawn } from 'p-spawn';
|
2 | import { GetPodResponse, KPod, PodItem, PodItemFilter, toKPod } from './k8s-types';
|
3 | import { getConfigurationNames, Realm, renderRealmFile } from './realm';
|
4 | import { asNames, prompt } from './utils';
|
5 |
|
6 |
|
7 | export async function kcreate(realm: Realm, resourceNames?: string | string[]) {
|
8 | await kubectlFile(realm, 'create', resourceNames);
|
9 | }
|
10 |
|
11 | export async function kapply(realm: Realm, resourceNames?: string | string[]) {
|
12 | await kubectlFile(realm, 'apply', resourceNames);
|
13 | }
|
14 |
|
15 |
|
16 | export async function kdel(realm: Realm, resourceNames?: string | string[]) {
|
17 | if (realm.confirmOnDelete) {
|
18 | const answer = await prompt("Do you really want to delete? (YES to continue)");
|
19 | if (answer !== "YES") {
|
20 | console.log(`Operation not confirmed. Exiting (nothing done).`);
|
21 | return;
|
22 | }
|
23 | }
|
24 | await kubectlFile(realm, 'delete', resourceNames);
|
25 | }
|
26 |
|
27 | let currentLogPodName: string | null = null;
|
28 |
|
29 | export async function klogs(realm: Realm, resourceNames?: string | string[]) {
|
30 | const names = await getConfigurationNames(realm, resourceNames);
|
31 | const pods = await fetchK8sObjectsByType(realm, 'pods');
|
32 |
|
33 | for (let serviceName of names) {
|
34 | const kpods = await getKPods(pods, { labels: { run: `${realm.system}-${serviceName}` } });
|
35 |
|
36 | for (const kpod of kpods) {
|
37 | const podName = kpod.name;
|
38 | for (const ctn of kpod.containers) {
|
39 | const ctnName = ctn.name;
|
40 |
|
41 | const args = ['logs', '-f', podName, '-c', ctnName];
|
42 | addNamespaceIfDefined(realm, args);
|
43 | console.log(`Will output logs for -> kubectl ${args.join(' ')}`);
|
44 |
|
45 | spawn('kubectl', args, {
|
46 | detached: true,
|
47 | onStdout: function (data) {
|
48 |
|
49 | if (currentLogPodName != null && currentLogPodName !== podName) {
|
50 | console.log('-----------\n');
|
51 | }
|
52 |
|
53 |
|
54 | if (currentLogPodName !== podName) {
|
55 | console.log(`----- LOG for: ${serviceName} / ${podName} / ${ctnName}`);
|
56 | currentLogPodName = podName;
|
57 | }
|
58 |
|
59 |
|
60 | process.stdout.write(data);
|
61 | }
|
62 | });
|
63 | }
|
64 |
|
65 | }
|
66 | }
|
67 | }
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 | export async function kshRestart(realm: Realm, serviceNamesStr: string) {
|
79 | const serviceNames = asNames(serviceNamesStr);
|
80 |
|
81 | const pods = await fetchK8sObjectsByType(realm, 'pods');
|
82 |
|
83 | for (let serviceName of serviceNames) {
|
84 | const podNames = await getPodNames(pods, { labels: { run: `${realm.system}-${serviceName}` } });
|
85 |
|
86 | for (let podName of podNames) {
|
87 |
|
88 |
|
89 | try {
|
90 | const args = ['exec', podName];
|
91 | addNamespaceIfDefined(realm, args);
|
92 | args.push('--', 'test', '-e', '/service/restart.sh');
|
93 | await spawn('kubectl', args, { toConsole: false });
|
94 | } catch (ex) {
|
95 | console.log(`Skipping service ${serviceName} - '/service/restart.sh' not found.`);
|
96 | continue;
|
97 | }
|
98 | console.log(`\n--- Restarting: ${serviceName} (pod: ${podName})`);
|
99 | const args = ['exec', podName];
|
100 | addNamespaceIfDefined(realm, args);
|
101 | args.push('--', '/service/restart.sh');
|
102 | await spawn('kubectl', args);
|
103 | console.log(`--- DONE: ${serviceName} : ${podName}`);
|
104 | }
|
105 | }
|
106 |
|
107 |
|
108 | }
|
109 |
|
110 | export async function kexec(realm: Realm, serviceNamesStr: string, commandAndArgs: string[]) {
|
111 | const serviceNames = asNames(serviceNamesStr);
|
112 | const pods = await fetchK8sObjectsByType(realm, 'pods');
|
113 |
|
114 | for (let serviceName of serviceNames) {
|
115 | const podNames = await getPodNames(pods, { labels: { run: `${realm.system}-${serviceName}` } });
|
116 |
|
117 | for (let podName of podNames) {
|
118 |
|
119 | try {
|
120 | let args = ['exec', podName]
|
121 | addNamespaceIfDefined(realm, args);
|
122 |
|
123 | args.push('--');
|
124 | args = args.concat(commandAndArgs);
|
125 | await spawn('kubectl', args);
|
126 | } catch (ex) {
|
127 | console.log(`Cannot run ${commandAndArgs} on pod ${podName} because ${ex}`);
|
128 | continue;
|
129 | }
|
130 | }
|
131 | }
|
132 | }
|
133 |
|
134 |
|
135 |
|
136 |
|
137 | export async function getCurrentContext(): Promise<string | null> {
|
138 |
|
139 | const psResult = await spawn('kubectl', ['config', 'current-context'], { capture: ['stdout', 'stderr'], ignoreFail: true });
|
140 | if (psResult.stderr) {
|
141 |
|
142 | return null;
|
143 | } else {
|
144 | return psResult.stdout!.toString().trim() as string;
|
145 | }
|
146 |
|
147 | }
|
148 |
|
149 | export async function setCurrentContext(name: string) {
|
150 |
|
151 |
|
152 | await spawn('kubectl', ['config', 'use-context', name]);
|
153 | }
|
154 |
|
155 |
|
156 |
|
157 |
|
158 | type KubectlAction = 'create' | 'apply' | 'delete';
|
159 | async function kubectlFile(realm: Realm, action: KubectlAction, resourceNames?: string | string[]) {
|
160 | const names = await getConfigurationNames(realm, resourceNames);
|
161 | for (let name of names) {
|
162 | const fileName = await renderRealmFile(realm, name);
|
163 | try {
|
164 | const args = [action, '-f', fileName];
|
165 | addNamespaceIfDefined(realm, args);
|
166 | await spawn('kubectl', args);
|
167 | } catch (ex) {
|
168 | console.log(`Can't k${action} ${fileName}, skipping`);
|
169 | }
|
170 | console.log();
|
171 | }
|
172 | }
|
173 |
|
174 | function addNamespaceIfDefined(realm: Realm, args: string[]) {
|
175 | const namespace = realm.namespace;
|
176 | if (namespace) {
|
177 | args.push('--namespace', namespace);
|
178 | }
|
179 | }
|
180 |
|
181 |
|
182 | async function fetchK8sObjectsByType(realm: Realm, type: string) {
|
183 | const args = ['get', type, '-o', 'json'];
|
184 | addNamespaceIfDefined(realm, args);
|
185 | const psResult = await spawn('kubectl', args, { capture: 'stdout' });
|
186 | const podsJsonStr = psResult.stdout!.toString();
|
187 | const response = JSON.parse(podsJsonStr) as GetPodResponse;
|
188 | return response.items || [];
|
189 | }
|
190 |
|
191 |
|
192 |
|
193 | function getPodNames(pods: PodItem[], filter?: { imageName?: string, labels?: { [key: string]: string } }) {
|
194 | const kpods = getKPods(pods, filter);
|
195 | return kpods.map(kpod => kpod.name);
|
196 | }
|
197 |
|
198 |
|
199 |
|
200 |
|
201 |
|
202 |
|
203 | function getKPods(pods: PodItem[], filter?: PodItemFilter): KPod[] {
|
204 | const kpods: KPod[] = [];
|
205 | for (let item of pods) {
|
206 | let pass = true;
|
207 |
|
208 | const itemName = item.metadata.name;
|
209 |
|
210 |
|
211 | if (filter && filter.imageName) {
|
212 |
|
213 | const itemImageName = (item.spec && item.spec.containers) ? item.spec.containers[0].image : null;
|
214 | if (itemImageName != null && itemImageName.startsWith(filter.imageName)) {
|
215 | pass = true;
|
216 | } else {
|
217 |
|
218 | continue;
|
219 | }
|
220 | }
|
221 |
|
222 |
|
223 | if (filter && filter.labels) {
|
224 | for (let labelName in filter.labels) {
|
225 | if (item.metadata.labels[labelName] === filter.labels[labelName]) {
|
226 | pass = true;
|
227 | } else {
|
228 | pass = false;
|
229 | continue;
|
230 | }
|
231 | }
|
232 | }
|
233 |
|
234 |
|
235 | if (pass) {
|
236 | kpods.push(toKPod(item));
|
237 | }
|
238 | }
|
239 |
|
240 | return kpods;
|
241 | }
|
242 |
|
243 |
|
\ | No newline at end of file |