@kubernetesjs/cli 0.0.3 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +54 -55
- package/commands/apply.d.ts +4 -0
- package/commands/apply.js +171 -0
- package/commands/cluster-info.d.ts +4 -0
- package/commands/cluster-info.js +26 -0
- package/commands/config-handler.d.ts +11 -0
- package/commands/config-handler.js +81 -0
- package/commands/config.d.ts +4 -0
- package/commands/config.js +72 -0
- package/commands/delete.d.ts +4 -0
- package/commands/delete.js +256 -0
- package/commands/deploy.d.ts +6 -0
- package/commands/deploy.js +209 -0
- package/commands/describe.d.ts +4 -0
- package/commands/describe.js +216 -0
- package/commands/exec.d.ts +4 -0
- package/commands/exec.js +145 -0
- package/commands/get.d.ts +4 -0
- package/commands/get.js +164 -0
- package/commands/logs.d.ts +4 -0
- package/commands/logs.js +110 -0
- package/commands/port-forward.d.ts +4 -0
- package/commands/port-forward.js +143 -0
- package/commands.d.ts +3 -0
- package/commands.js +93 -0
- package/config.d.ts +22 -0
- package/config.js +113 -0
- package/esm/commands/apply.js +133 -0
- package/esm/commands/cluster-info.js +21 -0
- package/esm/commands/config-handler.js +43 -0
- package/esm/commands/config.js +67 -0
- package/esm/commands/delete.js +218 -0
- package/esm/commands/deploy.js +207 -0
- package/esm/commands/describe.js +211 -0
- package/esm/commands/exec.js +140 -0
- package/esm/commands/get.js +159 -0
- package/esm/commands/logs.js +105 -0
- package/esm/commands/port-forward.js +138 -0
- package/esm/commands.js +86 -0
- package/esm/config.js +74 -0
- package/esm/index.js +19 -0
- package/esm/package.js +26 -0
- package/esm/utils.js +49 -0
- package/index.d.ts +3 -0
- package/index.js +22 -0
- package/package.d.ts +1 -0
- package/package.js +29 -0
- package/package.json +37 -61
- package/utils.d.ts +11 -0
- package/utils.js +58 -0
- package/main/client.js +0 -156
- package/main/index.js +0 -2598
- package/module/client.js +0 -129
- package/module/index.js +0 -2594
- package/src/client.ts +0 -156
- package/src/index.ts +0 -14187
- package/types/client.d.ts +0 -31
- package/types/index.d.ts +0 -11331
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { KubernetesClient } from 'kubernetesjs';
|
|
2
|
+
const createDeployment = (type) => {
|
|
3
|
+
const commonMetadata = {
|
|
4
|
+
name: `${type}-deployment`,
|
|
5
|
+
labels: {
|
|
6
|
+
app: type
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
const commonSpec = {
|
|
10
|
+
replicas: 1,
|
|
11
|
+
selector: {
|
|
12
|
+
matchLabels: {
|
|
13
|
+
app: type
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
template: {
|
|
17
|
+
metadata: {
|
|
18
|
+
labels: {
|
|
19
|
+
app: type
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
switch (type) {
|
|
25
|
+
case 'minio':
|
|
26
|
+
return {
|
|
27
|
+
apiVersion: 'apps/v1',
|
|
28
|
+
kind: 'Deployment',
|
|
29
|
+
metadata: commonMetadata,
|
|
30
|
+
spec: {
|
|
31
|
+
...commonSpec,
|
|
32
|
+
template: {
|
|
33
|
+
...commonSpec.template,
|
|
34
|
+
spec: {
|
|
35
|
+
containers: [{
|
|
36
|
+
name: 'minio',
|
|
37
|
+
image: 'minio/minio:latest',
|
|
38
|
+
ports: [{ containerPort: 9000, name: 'minio' }],
|
|
39
|
+
env: [
|
|
40
|
+
{ name: 'MINIO_ROOT_USER', value: 'minioadmin' },
|
|
41
|
+
{ name: 'MINIO_ROOT_PASSWORD', value: 'minioadmin' }
|
|
42
|
+
],
|
|
43
|
+
args: ['server', '/data']
|
|
44
|
+
}]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
case 'postgres':
|
|
50
|
+
return {
|
|
51
|
+
apiVersion: 'apps/v1',
|
|
52
|
+
kind: 'Deployment',
|
|
53
|
+
metadata: commonMetadata,
|
|
54
|
+
spec: {
|
|
55
|
+
...commonSpec,
|
|
56
|
+
template: {
|
|
57
|
+
...commonSpec.template,
|
|
58
|
+
spec: {
|
|
59
|
+
containers: [{
|
|
60
|
+
name: 'postgres',
|
|
61
|
+
image: 'pyramation/pgvector:13.3-alpine',
|
|
62
|
+
ports: [{ containerPort: 5432, name: 'postgres' }],
|
|
63
|
+
env: [
|
|
64
|
+
{ name: 'POSTGRES_USER', value: 'postgres' },
|
|
65
|
+
{ name: 'POSTGRES_PASSWORD', value: 'postgres' },
|
|
66
|
+
{ name: 'POSTGRES_DB', value: 'postgres' }
|
|
67
|
+
]
|
|
68
|
+
}]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
case 'ollama':
|
|
74
|
+
return {
|
|
75
|
+
apiVersion: 'apps/v1',
|
|
76
|
+
kind: 'Deployment',
|
|
77
|
+
metadata: commonMetadata,
|
|
78
|
+
spec: {
|
|
79
|
+
...commonSpec,
|
|
80
|
+
template: {
|
|
81
|
+
...commonSpec.template,
|
|
82
|
+
spec: {
|
|
83
|
+
containers: [{
|
|
84
|
+
name: 'ollama',
|
|
85
|
+
image: 'ollama/ollama:latest',
|
|
86
|
+
ports: [{ containerPort: 11434, name: 'ollama' }]
|
|
87
|
+
}]
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
default:
|
|
93
|
+
throw new Error(`Unsupported deployment type: ${type}`);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
const createService = (type) => {
|
|
97
|
+
const commonMetadata = {
|
|
98
|
+
name: `${type}-service`,
|
|
99
|
+
labels: {
|
|
100
|
+
app: type
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const commonSpec = {
|
|
104
|
+
selector: {
|
|
105
|
+
app: type
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
switch (type) {
|
|
109
|
+
case 'minio':
|
|
110
|
+
return {
|
|
111
|
+
apiVersion: 'v1',
|
|
112
|
+
kind: 'Service',
|
|
113
|
+
metadata: commonMetadata,
|
|
114
|
+
spec: {
|
|
115
|
+
...commonSpec,
|
|
116
|
+
ports: [{ port: 9000, targetPort: 'minio' }],
|
|
117
|
+
type: 'ClusterIP'
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
case 'postgres':
|
|
121
|
+
return {
|
|
122
|
+
apiVersion: 'v1',
|
|
123
|
+
kind: 'Service',
|
|
124
|
+
metadata: commonMetadata,
|
|
125
|
+
spec: {
|
|
126
|
+
...commonSpec,
|
|
127
|
+
ports: [{ port: 5432, targetPort: 'postgres' }],
|
|
128
|
+
type: 'ClusterIP'
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
case 'ollama':
|
|
132
|
+
return {
|
|
133
|
+
apiVersion: 'v1',
|
|
134
|
+
kind: 'Service',
|
|
135
|
+
metadata: commonMetadata,
|
|
136
|
+
spec: {
|
|
137
|
+
...commonSpec,
|
|
138
|
+
ports: [{ port: 11434, targetPort: 'ollama' }],
|
|
139
|
+
type: 'ClusterIP'
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
default:
|
|
143
|
+
throw new Error(`Unsupported service type: ${type}`);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
export default async (argv, prompter, _options) => {
|
|
147
|
+
const deploymentOptions = ['minio', 'postgres', 'ollama'];
|
|
148
|
+
const questions = [
|
|
149
|
+
{
|
|
150
|
+
type: 'autocomplete',
|
|
151
|
+
name: 'deploymentType',
|
|
152
|
+
message: 'Select deployment type',
|
|
153
|
+
options: deploymentOptions,
|
|
154
|
+
maxDisplayLines: 5,
|
|
155
|
+
required: true
|
|
156
|
+
}
|
|
157
|
+
];
|
|
158
|
+
const { deploymentType } = await prompter.prompt(argv, questions);
|
|
159
|
+
const confirmQuestion = {
|
|
160
|
+
type: 'confirm',
|
|
161
|
+
name: 'confirmDeployment',
|
|
162
|
+
message: `Are you sure you want to deploy ${deploymentType}?`,
|
|
163
|
+
required: true
|
|
164
|
+
};
|
|
165
|
+
const { confirmDeployment } = await prompter.prompt(argv, [confirmQuestion]);
|
|
166
|
+
if (!confirmDeployment) {
|
|
167
|
+
console.log('Deployment cancelled.');
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
console.log(`Deploying ${deploymentType}...`);
|
|
171
|
+
try {
|
|
172
|
+
// Initialize Kubernetes client
|
|
173
|
+
const client = new KubernetesClient({
|
|
174
|
+
restEndpoint: argv.clientUrl
|
|
175
|
+
});
|
|
176
|
+
// Create deployment
|
|
177
|
+
const deployment = createDeployment(deploymentType);
|
|
178
|
+
await client.createAppsV1NamespacedDeployment({
|
|
179
|
+
path: {
|
|
180
|
+
namespace: 'default'
|
|
181
|
+
},
|
|
182
|
+
query: {
|
|
183
|
+
pretty: 'true'
|
|
184
|
+
},
|
|
185
|
+
body: deployment
|
|
186
|
+
});
|
|
187
|
+
console.log(`Created deployment: ${deployment.metadata.name}`);
|
|
188
|
+
// Create service
|
|
189
|
+
const service = createService(deploymentType);
|
|
190
|
+
await client.createCoreV1NamespacedService({
|
|
191
|
+
path: {
|
|
192
|
+
namespace: 'default'
|
|
193
|
+
},
|
|
194
|
+
query: {
|
|
195
|
+
pretty: 'true'
|
|
196
|
+
},
|
|
197
|
+
body: service
|
|
198
|
+
});
|
|
199
|
+
console.log(`Created service: ${service.metadata.name}`);
|
|
200
|
+
console.log(`Successfully deployed ${deploymentType}!`);
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
console.error(`Failed to deploy ${deploymentType}:`, error);
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
return { deploymentType };
|
|
207
|
+
};
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { KubernetesClient } from 'kubernetesjs';
|
|
3
|
+
import { getCurrentNamespace } from '../config';
|
|
4
|
+
async function promptResourceType(prompter, argv) {
|
|
5
|
+
const resourceTypes = [
|
|
6
|
+
'pod',
|
|
7
|
+
'service',
|
|
8
|
+
'deployment',
|
|
9
|
+
'replicaset',
|
|
10
|
+
'statefulset',
|
|
11
|
+
'daemonset',
|
|
12
|
+
'configmap',
|
|
13
|
+
'secret',
|
|
14
|
+
'namespace'
|
|
15
|
+
];
|
|
16
|
+
const question = {
|
|
17
|
+
type: 'autocomplete',
|
|
18
|
+
name: 'resourceType',
|
|
19
|
+
message: 'Select resource type',
|
|
20
|
+
options: resourceTypes,
|
|
21
|
+
maxDisplayLines: 10,
|
|
22
|
+
required: true
|
|
23
|
+
};
|
|
24
|
+
const { resourceType } = await prompter.prompt(argv, [question]);
|
|
25
|
+
return resourceType;
|
|
26
|
+
}
|
|
27
|
+
async function promptResourceName(prompter, argv, resourceType, namespace, client) {
|
|
28
|
+
let resources = [];
|
|
29
|
+
switch (resourceType) {
|
|
30
|
+
case 'pod':
|
|
31
|
+
const pods = await client.listCoreV1NamespacedPod({
|
|
32
|
+
path: { namespace },
|
|
33
|
+
query: { limit: 100 }
|
|
34
|
+
});
|
|
35
|
+
resources = pods.items || [];
|
|
36
|
+
break;
|
|
37
|
+
case 'service':
|
|
38
|
+
const services = await client.listCoreV1NamespacedService({
|
|
39
|
+
path: { namespace },
|
|
40
|
+
query: { limit: 100 }
|
|
41
|
+
});
|
|
42
|
+
resources = services.items || [];
|
|
43
|
+
break;
|
|
44
|
+
case 'deployment':
|
|
45
|
+
const deployments = await client.listAppsV1NamespacedDeployment({
|
|
46
|
+
path: { namespace },
|
|
47
|
+
query: { limit: 100 }
|
|
48
|
+
});
|
|
49
|
+
resources = deployments.items || [];
|
|
50
|
+
break;
|
|
51
|
+
default:
|
|
52
|
+
console.log(chalk.yellow(`Resource type '${resourceType}' not implemented yet for selection`));
|
|
53
|
+
return '';
|
|
54
|
+
}
|
|
55
|
+
if (resources.length === 0) {
|
|
56
|
+
console.log(chalk.yellow(`No ${resourceType}s found in namespace ${namespace}`));
|
|
57
|
+
return '';
|
|
58
|
+
}
|
|
59
|
+
const options = resources.map(r => ({
|
|
60
|
+
name: r.metadata.name,
|
|
61
|
+
value: r.metadata.name
|
|
62
|
+
}));
|
|
63
|
+
const question = {
|
|
64
|
+
type: 'autocomplete',
|
|
65
|
+
name: 'resourceName',
|
|
66
|
+
message: `Select ${resourceType} name`,
|
|
67
|
+
options,
|
|
68
|
+
maxDisplayLines: 10,
|
|
69
|
+
required: true
|
|
70
|
+
};
|
|
71
|
+
const { resourceName } = await prompter.prompt(argv, [question]);
|
|
72
|
+
return resourceName;
|
|
73
|
+
}
|
|
74
|
+
function describePod(pod) {
|
|
75
|
+
console.log(chalk.bold(`Name: ${pod.metadata.name}`));
|
|
76
|
+
console.log(chalk.bold(`Namespace: ${pod.metadata.namespace}`));
|
|
77
|
+
console.log(chalk.bold(`Priority: ${pod.spec.priority || 0}`));
|
|
78
|
+
console.log(chalk.bold(`Node: ${pod.spec.nodeName || '<none>'}`));
|
|
79
|
+
console.log(chalk.bold(`Start Time: ${pod.status.startTime || '<unknown>'}`));
|
|
80
|
+
console.log(chalk.bold('\nLabels:'));
|
|
81
|
+
if (pod.metadata.labels) {
|
|
82
|
+
Object.entries(pod.metadata.labels).forEach(([key, value]) => {
|
|
83
|
+
console.log(` ${key}=${value}`);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
console.log(chalk.bold('\nStatus: ' + pod.status.phase));
|
|
87
|
+
console.log(chalk.bold('IP: ' + pod.status.podIP));
|
|
88
|
+
console.log(chalk.bold('\nContainers:'));
|
|
89
|
+
if (pod.spec.containers) {
|
|
90
|
+
pod.spec.containers.forEach((container) => {
|
|
91
|
+
console.log(chalk.bold(` ${container.name}:`));
|
|
92
|
+
console.log(` Image: ${container.image}`);
|
|
93
|
+
console.log(` Ports: ${container.ports?.map((p) => p.containerPort).join(', ') || '<none>'}`);
|
|
94
|
+
const status = pod.status.containerStatuses?.find((s) => s.name === container.name);
|
|
95
|
+
if (status) {
|
|
96
|
+
console.log(` Ready: ${status.ready}`);
|
|
97
|
+
console.log(` Restarts: ${status.restartCount}`);
|
|
98
|
+
const stateEntries = Object.entries(status.state || {});
|
|
99
|
+
if (stateEntries.length > 0) {
|
|
100
|
+
const [state, details] = stateEntries[0];
|
|
101
|
+
console.log(` State: ${state}`);
|
|
102
|
+
if (details && typeof details === 'object') {
|
|
103
|
+
Object.entries(details).forEach(([key, value]) => {
|
|
104
|
+
console.log(` ${key}: ${value}`);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
console.log('');
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
console.log(chalk.bold('Events:'));
|
|
113
|
+
console.log(' <Events not available in this implementation>');
|
|
114
|
+
}
|
|
115
|
+
function describeService(service) {
|
|
116
|
+
console.log(chalk.bold(`Name: ${service.metadata.name}`));
|
|
117
|
+
console.log(chalk.bold(`Namespace: ${service.metadata.namespace}`));
|
|
118
|
+
console.log(chalk.bold(`Labels: ${JSON.stringify(service.metadata.labels || {})}`));
|
|
119
|
+
console.log(chalk.bold(`Selector: ${JSON.stringify(service.spec.selector || {})}`));
|
|
120
|
+
console.log(chalk.bold(`Type: ${service.spec.type}`));
|
|
121
|
+
console.log(chalk.bold(`IP: ${service.spec.clusterIP}`));
|
|
122
|
+
if (service.spec.ports) {
|
|
123
|
+
console.log(chalk.bold('\nPorts:'));
|
|
124
|
+
service.spec.ports.forEach((port) => {
|
|
125
|
+
console.log(` ${port.name || '<unnamed>'}: ${port.port}/${port.protocol} -> ${port.targetPort}`);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
console.log(chalk.bold('\nSession Affinity: ' + (service.spec.sessionAffinity || 'None')));
|
|
129
|
+
console.log(chalk.bold('\nEvents:'));
|
|
130
|
+
console.log(' <Events not available in this implementation>');
|
|
131
|
+
}
|
|
132
|
+
function describeDeployment(deployment) {
|
|
133
|
+
console.log(chalk.bold(`Name: ${deployment.metadata.name}`));
|
|
134
|
+
console.log(chalk.bold(`Namespace: ${deployment.metadata.namespace}`));
|
|
135
|
+
console.log(chalk.bold(`CreationTimestamp: ${deployment.metadata.creationTimestamp}`));
|
|
136
|
+
console.log(chalk.bold(`Labels: ${JSON.stringify(deployment.metadata.labels || {})}`));
|
|
137
|
+
console.log(chalk.bold(`Annotations: ${JSON.stringify(deployment.metadata.annotations || {})}`));
|
|
138
|
+
console.log(chalk.bold(`Selector: ${JSON.stringify(deployment.spec.selector.matchLabels || {})}`));
|
|
139
|
+
console.log(chalk.bold(`Replicas: ${deployment.status.replicas || 0} desired | ${deployment.status.updatedReplicas || 0} updated | ${deployment.status.readyReplicas || 0} ready | ${deployment.status.availableReplicas || 0} available`));
|
|
140
|
+
console.log(chalk.bold(`StrategyType: ${deployment.spec.strategy.type}`));
|
|
141
|
+
if (deployment.spec.template && deployment.spec.template.spec.containers) {
|
|
142
|
+
console.log(chalk.bold('\nContainers:'));
|
|
143
|
+
deployment.spec.template.spec.containers.forEach((container) => {
|
|
144
|
+
console.log(chalk.bold(` ${container.name}:`));
|
|
145
|
+
console.log(` Image: ${container.image}`);
|
|
146
|
+
console.log(` Ports: ${container.ports?.map((p) => p.containerPort).join(', ') || '<none>'}`);
|
|
147
|
+
console.log(` Environment: ${container.env?.map((e) => `${e.name}=${e.value}`).join(', ') || '<none>'}`);
|
|
148
|
+
console.log('');
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
if (deployment.status.conditions) {
|
|
152
|
+
console.log(chalk.bold('\nConditions:'));
|
|
153
|
+
console.log(chalk.bold(' Type Status Reason'));
|
|
154
|
+
deployment.status.conditions.forEach((condition) => {
|
|
155
|
+
console.log(` ${condition.type.padEnd(15)}${condition.status.padEnd(8)}${condition.reason || ''}`);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
console.log(chalk.bold('\nEvents:'));
|
|
159
|
+
console.log(' <Events not available in this implementation>');
|
|
160
|
+
}
|
|
161
|
+
export default async (argv, prompter, _options) => {
|
|
162
|
+
try {
|
|
163
|
+
const client = new KubernetesClient({
|
|
164
|
+
restEndpoint: 'http://localhost:8001' // Default kube-proxy endpoint
|
|
165
|
+
});
|
|
166
|
+
const namespace = argv.n || argv.namespace || getCurrentNamespace();
|
|
167
|
+
const resourceType = argv._?.[0] || await promptResourceType(prompter, argv);
|
|
168
|
+
const resourceName = argv._?.[1] || await promptResourceName(prompter, argv, resourceType, namespace, client);
|
|
169
|
+
if (!resourceName) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
console.log(chalk.blue(`Describing ${resourceType} ${resourceName} in namespace ${namespace}...`));
|
|
173
|
+
switch (resourceType) {
|
|
174
|
+
case 'pod':
|
|
175
|
+
const pod = await client.readCoreV1NamespacedPod({
|
|
176
|
+
path: {
|
|
177
|
+
namespace,
|
|
178
|
+
name: resourceName
|
|
179
|
+
},
|
|
180
|
+
query: {}
|
|
181
|
+
});
|
|
182
|
+
describePod(pod);
|
|
183
|
+
break;
|
|
184
|
+
case 'service':
|
|
185
|
+
const service = await client.readCoreV1NamespacedService({
|
|
186
|
+
path: {
|
|
187
|
+
namespace,
|
|
188
|
+
name: resourceName
|
|
189
|
+
},
|
|
190
|
+
query: {}
|
|
191
|
+
});
|
|
192
|
+
describeService(service);
|
|
193
|
+
break;
|
|
194
|
+
case 'deployment':
|
|
195
|
+
const deployment = await client.readAppsV1NamespacedDeployment({
|
|
196
|
+
path: {
|
|
197
|
+
namespace,
|
|
198
|
+
name: resourceName
|
|
199
|
+
},
|
|
200
|
+
query: {}
|
|
201
|
+
});
|
|
202
|
+
describeDeployment(deployment);
|
|
203
|
+
break;
|
|
204
|
+
default:
|
|
205
|
+
console.log(chalk.yellow(`Resource type '${resourceType}' not implemented yet`));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
console.error(chalk.red(`Error: ${error}`));
|
|
210
|
+
}
|
|
211
|
+
};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { KubernetesClient } from 'kubernetesjs';
|
|
3
|
+
import { getCurrentNamespace } from '../config';
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
async function promptPodName(prompter, argv, namespace, client) {
|
|
6
|
+
try {
|
|
7
|
+
const pods = await client.listCoreV1NamespacedPod({
|
|
8
|
+
path: { namespace },
|
|
9
|
+
query: { limit: 100 }
|
|
10
|
+
});
|
|
11
|
+
if (!pods.items || pods.items.length === 0) {
|
|
12
|
+
console.log(chalk.yellow(`No pods found in namespace ${namespace}`));
|
|
13
|
+
return '';
|
|
14
|
+
}
|
|
15
|
+
const options = pods.items.map(pod => ({
|
|
16
|
+
name: pod.metadata.name,
|
|
17
|
+
value: pod.metadata.name
|
|
18
|
+
}));
|
|
19
|
+
const question = {
|
|
20
|
+
type: 'autocomplete',
|
|
21
|
+
name: 'podName',
|
|
22
|
+
message: 'Select pod',
|
|
23
|
+
options,
|
|
24
|
+
maxDisplayLines: 10,
|
|
25
|
+
required: true
|
|
26
|
+
};
|
|
27
|
+
const { podName } = await prompter.prompt(argv, [question]);
|
|
28
|
+
return podName;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
console.error(chalk.red(`Error getting pods: ${error}`));
|
|
32
|
+
return '';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function promptContainerName(prompter, argv, namespace, podName, client) {
|
|
36
|
+
try {
|
|
37
|
+
const pod = await client.readCoreV1NamespacedPod({
|
|
38
|
+
path: {
|
|
39
|
+
namespace,
|
|
40
|
+
name: podName
|
|
41
|
+
},
|
|
42
|
+
query: {}
|
|
43
|
+
});
|
|
44
|
+
if (!pod.spec || !pod.spec.containers || pod.spec.containers.length === 0) {
|
|
45
|
+
console.log(chalk.yellow(`No containers found in pod ${podName}`));
|
|
46
|
+
return '';
|
|
47
|
+
}
|
|
48
|
+
if (pod.spec.containers.length === 1) {
|
|
49
|
+
return pod.spec.containers[0].name;
|
|
50
|
+
}
|
|
51
|
+
const options = pod.spec.containers.map(container => ({
|
|
52
|
+
name: container.name,
|
|
53
|
+
value: container.name
|
|
54
|
+
}));
|
|
55
|
+
const question = {
|
|
56
|
+
type: 'autocomplete',
|
|
57
|
+
name: 'containerName',
|
|
58
|
+
message: 'Select container',
|
|
59
|
+
options,
|
|
60
|
+
maxDisplayLines: 10,
|
|
61
|
+
required: true
|
|
62
|
+
};
|
|
63
|
+
const { containerName } = await prompter.prompt(argv, [question]);
|
|
64
|
+
return containerName;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
console.error(chalk.red(`Error getting containers: ${error}`));
|
|
68
|
+
return '';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function execInPod(namespace, podName, containerName, command) {
|
|
72
|
+
console.log(chalk.blue(`Executing command in ${containerName ? 'container ' + containerName + ' of ' : ''}pod ${podName} in namespace ${namespace}...`));
|
|
73
|
+
const kubectlArgs = [
|
|
74
|
+
'exec',
|
|
75
|
+
'-it',
|
|
76
|
+
'-n', namespace,
|
|
77
|
+
podName
|
|
78
|
+
];
|
|
79
|
+
if (containerName) {
|
|
80
|
+
kubectlArgs.push('-c', containerName);
|
|
81
|
+
}
|
|
82
|
+
kubectlArgs.push('--', ...command);
|
|
83
|
+
const kubectl = spawn('kubectl', kubectlArgs, {
|
|
84
|
+
stdio: 'inherit',
|
|
85
|
+
shell: true
|
|
86
|
+
});
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
kubectl.on('close', (code) => {
|
|
89
|
+
if (code === 0) {
|
|
90
|
+
resolve();
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
reject(new Error(`kubectl exec exited with code ${code}`));
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
kubectl.on('error', (error) => {
|
|
97
|
+
reject(error);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
export default async (argv, prompter, _options) => {
|
|
102
|
+
try {
|
|
103
|
+
const client = new KubernetesClient({
|
|
104
|
+
restEndpoint: argv.clientUrl
|
|
105
|
+
});
|
|
106
|
+
const namespace = argv.n || argv.namespace || getCurrentNamespace();
|
|
107
|
+
const podName = argv._?.[0] || await promptPodName(prompter, argv, namespace, client);
|
|
108
|
+
if (!podName) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
let containerName = argv.c || argv.container;
|
|
112
|
+
if (!containerName) {
|
|
113
|
+
containerName = await promptContainerName(prompter, argv, namespace, podName, client);
|
|
114
|
+
if (!containerName) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
let command = [];
|
|
119
|
+
const dashIndex = argv._.findIndex(arg => arg === '--');
|
|
120
|
+
if (dashIndex !== -1 && dashIndex < argv._.length - 1) {
|
|
121
|
+
command = argv._.slice(dashIndex + 1);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
const commandQuestion = {
|
|
125
|
+
type: 'text',
|
|
126
|
+
name: 'command',
|
|
127
|
+
message: 'Enter command to execute',
|
|
128
|
+
required: true,
|
|
129
|
+
default: '/bin/sh',
|
|
130
|
+
useDefault: true
|
|
131
|
+
};
|
|
132
|
+
const { command: cmd } = await prompter.prompt(argv, [commandQuestion]);
|
|
133
|
+
command = cmd.split(' ');
|
|
134
|
+
}
|
|
135
|
+
await execInPod(namespace, podName, containerName, command);
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
console.error(chalk.red(`Error: ${error}`));
|
|
139
|
+
}
|
|
140
|
+
};
|