@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,159 @@
|
|
|
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
|
+
'pods',
|
|
7
|
+
'services',
|
|
8
|
+
'deployments',
|
|
9
|
+
'replicasets',
|
|
10
|
+
'statefulsets',
|
|
11
|
+
'daemonsets',
|
|
12
|
+
'configmaps',
|
|
13
|
+
'secrets',
|
|
14
|
+
'namespaces',
|
|
15
|
+
'all'
|
|
16
|
+
];
|
|
17
|
+
const question = {
|
|
18
|
+
type: 'autocomplete',
|
|
19
|
+
name: 'resourceType',
|
|
20
|
+
message: 'Select resource type',
|
|
21
|
+
options: resourceTypes,
|
|
22
|
+
maxDisplayLines: 10,
|
|
23
|
+
required: true
|
|
24
|
+
};
|
|
25
|
+
const { resourceType } = await prompter.prompt(argv, [question]);
|
|
26
|
+
return resourceType;
|
|
27
|
+
}
|
|
28
|
+
function formatPodData(pod) {
|
|
29
|
+
const name = pod.metadata.name;
|
|
30
|
+
const ready = `${pod.status.containerStatuses?.filter((c) => c.ready).length || 0}/${pod.status.containerStatuses?.length || 0}`;
|
|
31
|
+
const status = pod.status.phase;
|
|
32
|
+
const restarts = pod.status.containerStatuses?.reduce((sum, c) => sum + c.restartCount, 0) || 0;
|
|
33
|
+
const age = new Date(pod.metadata.creationTimestamp).toLocaleString();
|
|
34
|
+
console.log(chalk.green(name.padEnd(50)) +
|
|
35
|
+
ready.padEnd(10) +
|
|
36
|
+
status.padEnd(15) +
|
|
37
|
+
restarts.toString().padEnd(10) +
|
|
38
|
+
age);
|
|
39
|
+
}
|
|
40
|
+
function formatServiceData(service) {
|
|
41
|
+
const name = service.metadata.name;
|
|
42
|
+
const type = service.spec.type;
|
|
43
|
+
const clusterIP = service.spec.clusterIP;
|
|
44
|
+
const externalIP = service.spec.externalIPs?.join(',') || '<none>';
|
|
45
|
+
const ports = service.spec.ports?.map((p) => `${p.port}:${p.targetPort}`).join(',') || '<none>';
|
|
46
|
+
const age = new Date(service.metadata.creationTimestamp).toLocaleString();
|
|
47
|
+
console.log(chalk.green(name.padEnd(30)) +
|
|
48
|
+
type.padEnd(15) +
|
|
49
|
+
clusterIP.padEnd(20) +
|
|
50
|
+
externalIP.padEnd(20) +
|
|
51
|
+
ports.padEnd(20) +
|
|
52
|
+
age);
|
|
53
|
+
}
|
|
54
|
+
function formatDeploymentData(deployment) {
|
|
55
|
+
const name = deployment.metadata.name;
|
|
56
|
+
const ready = `${deployment.status.readyReplicas || 0}/${deployment.status.replicas || 0}`;
|
|
57
|
+
const upToDate = deployment.status.updatedReplicas || 0;
|
|
58
|
+
const available = deployment.status.availableReplicas || 0;
|
|
59
|
+
const age = new Date(deployment.metadata.creationTimestamp).toLocaleString();
|
|
60
|
+
console.log(chalk.green(name.padEnd(30)) +
|
|
61
|
+
ready.padEnd(10) +
|
|
62
|
+
upToDate.toString().padEnd(10) +
|
|
63
|
+
available.toString().padEnd(10) +
|
|
64
|
+
age);
|
|
65
|
+
}
|
|
66
|
+
async function getAllResources(client, namespace) {
|
|
67
|
+
try {
|
|
68
|
+
const pods = await client.listCoreV1NamespacedPod({
|
|
69
|
+
path: { namespace },
|
|
70
|
+
query: { limit: 100 }
|
|
71
|
+
});
|
|
72
|
+
if (pods.items && pods.items.length > 0) {
|
|
73
|
+
console.log(chalk.bold('\nPODS:'));
|
|
74
|
+
console.log(chalk.bold('NAME'.padEnd(50) + 'READY'.padEnd(10) + 'STATUS'.padEnd(15) + 'RESTARTS'.padEnd(10) + 'AGE'));
|
|
75
|
+
pods.items.forEach(formatPodData);
|
|
76
|
+
}
|
|
77
|
+
const services = await client.listCoreV1NamespacedService({
|
|
78
|
+
path: { namespace },
|
|
79
|
+
query: { limit: 100 }
|
|
80
|
+
});
|
|
81
|
+
if (services.items && services.items.length > 0) {
|
|
82
|
+
console.log(chalk.bold('\nSERVICES:'));
|
|
83
|
+
console.log(chalk.bold('NAME'.padEnd(30) + 'TYPE'.padEnd(15) + 'CLUSTER-IP'.padEnd(20) + 'EXTERNAL-IP'.padEnd(20) + 'PORT(S)'.padEnd(20) + 'AGE'));
|
|
84
|
+
services.items.forEach(formatServiceData);
|
|
85
|
+
}
|
|
86
|
+
const deployments = await client.listAppsV1NamespacedDeployment({
|
|
87
|
+
path: { namespace },
|
|
88
|
+
query: { limit: 100 }
|
|
89
|
+
});
|
|
90
|
+
if (deployments.items && deployments.items.length > 0) {
|
|
91
|
+
console.log(chalk.bold('\nDEPLOYMENTS:'));
|
|
92
|
+
console.log(chalk.bold('NAME'.padEnd(30) + 'READY'.padEnd(10) + 'UP-TO-DATE'.padEnd(10) + 'AVAILABLE'.padEnd(10) + 'AGE'));
|
|
93
|
+
deployments.items.forEach(formatDeploymentData);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
console.error(chalk.red(`Error getting resources: ${error}`));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export default async (argv, prompter, _options) => {
|
|
101
|
+
try {
|
|
102
|
+
const client = new KubernetesClient({
|
|
103
|
+
restEndpoint: argv.clientUrl
|
|
104
|
+
});
|
|
105
|
+
const namespace = argv.n || argv.namespace || getCurrentNamespace();
|
|
106
|
+
const resourceType = argv._?.[0] || await promptResourceType(prompter, argv);
|
|
107
|
+
console.log(chalk.blue(`Getting ${resourceType} in namespace ${namespace}...`));
|
|
108
|
+
if (resourceType === 'all') {
|
|
109
|
+
await getAllResources(client, namespace);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
switch (resourceType) {
|
|
113
|
+
case 'pods':
|
|
114
|
+
const pods = await client.listCoreV1NamespacedPod({
|
|
115
|
+
path: { namespace },
|
|
116
|
+
query: { limit: 100 }
|
|
117
|
+
});
|
|
118
|
+
console.log(chalk.bold('NAME'.padEnd(50) + 'READY'.padEnd(10) + 'STATUS'.padEnd(15) + 'RESTARTS'.padEnd(10) + 'AGE'));
|
|
119
|
+
if (pods.items && pods.items.length > 0) {
|
|
120
|
+
pods.items.forEach(formatPodData);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
console.log(chalk.yellow('No pods found'));
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
case 'services':
|
|
127
|
+
const services = await client.listCoreV1NamespacedService({
|
|
128
|
+
path: { namespace },
|
|
129
|
+
query: { limit: 100 }
|
|
130
|
+
});
|
|
131
|
+
console.log(chalk.bold('NAME'.padEnd(30) + 'TYPE'.padEnd(15) + 'CLUSTER-IP'.padEnd(20) + 'EXTERNAL-IP'.padEnd(20) + 'PORT(S)'.padEnd(20) + 'AGE'));
|
|
132
|
+
if (services.items && services.items.length > 0) {
|
|
133
|
+
services.items.forEach(formatServiceData);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
console.log(chalk.yellow('No services found'));
|
|
137
|
+
}
|
|
138
|
+
break;
|
|
139
|
+
case 'deployments':
|
|
140
|
+
const deployments = await client.listAppsV1NamespacedDeployment({
|
|
141
|
+
path: { namespace },
|
|
142
|
+
query: { limit: 100 }
|
|
143
|
+
});
|
|
144
|
+
console.log(chalk.bold('NAME'.padEnd(30) + 'READY'.padEnd(10) + 'UP-TO-DATE'.padEnd(10) + 'AVAILABLE'.padEnd(10) + 'AGE'));
|
|
145
|
+
if (deployments.items && deployments.items.length > 0) {
|
|
146
|
+
deployments.items.forEach(formatDeploymentData);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
console.log(chalk.yellow('No deployments found'));
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
default:
|
|
153
|
+
console.log(chalk.yellow(`Resource type '${resourceType}' not implemented yet`));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
console.error(chalk.red(`Error: ${error}`));
|
|
158
|
+
}
|
|
159
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { KubernetesClient } from 'kubernetesjs';
|
|
3
|
+
import { getCurrentNamespace } from '../config';
|
|
4
|
+
async function promptPodName(prompter, argv, namespace, client) {
|
|
5
|
+
try {
|
|
6
|
+
const pods = await client.listCoreV1NamespacedPod({
|
|
7
|
+
path: { namespace },
|
|
8
|
+
query: { limit: 100 }
|
|
9
|
+
});
|
|
10
|
+
if (!pods.items || pods.items.length === 0) {
|
|
11
|
+
console.log(chalk.yellow(`No pods found in namespace ${namespace}`));
|
|
12
|
+
return '';
|
|
13
|
+
}
|
|
14
|
+
const options = pods.items.map(pod => ({
|
|
15
|
+
name: pod.metadata.name,
|
|
16
|
+
value: pod.metadata.name
|
|
17
|
+
}));
|
|
18
|
+
const question = {
|
|
19
|
+
type: 'autocomplete',
|
|
20
|
+
name: 'podName',
|
|
21
|
+
message: 'Select pod',
|
|
22
|
+
options,
|
|
23
|
+
maxDisplayLines: 10,
|
|
24
|
+
required: true
|
|
25
|
+
};
|
|
26
|
+
const { podName } = await prompter.prompt(argv, [question]);
|
|
27
|
+
return podName;
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
console.error(chalk.red(`Error getting pods: ${error}`));
|
|
31
|
+
return '';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function promptContainerName(prompter, argv, namespace, podName, client) {
|
|
35
|
+
try {
|
|
36
|
+
const pod = await client.readCoreV1NamespacedPod({
|
|
37
|
+
path: {
|
|
38
|
+
namespace,
|
|
39
|
+
name: podName
|
|
40
|
+
},
|
|
41
|
+
query: {}
|
|
42
|
+
});
|
|
43
|
+
if (!pod.spec || !pod.spec.containers || pod.spec.containers.length === 0) {
|
|
44
|
+
console.log(chalk.yellow(`No containers found in pod ${podName}`));
|
|
45
|
+
return '';
|
|
46
|
+
}
|
|
47
|
+
if (pod.spec.containers.length === 1) {
|
|
48
|
+
return pod.spec.containers[0].name;
|
|
49
|
+
}
|
|
50
|
+
const options = pod.spec.containers.map(container => ({
|
|
51
|
+
name: container.name,
|
|
52
|
+
value: container.name
|
|
53
|
+
}));
|
|
54
|
+
const question = {
|
|
55
|
+
type: 'autocomplete',
|
|
56
|
+
name: 'containerName',
|
|
57
|
+
message: 'Select container',
|
|
58
|
+
options,
|
|
59
|
+
maxDisplayLines: 10,
|
|
60
|
+
required: true
|
|
61
|
+
};
|
|
62
|
+
const { containerName } = await prompter.prompt(argv, [question]);
|
|
63
|
+
return containerName;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
console.error(chalk.red(`Error getting containers: ${error}`));
|
|
67
|
+
return '';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export default async (argv, prompter, _options) => {
|
|
71
|
+
try {
|
|
72
|
+
const client = new KubernetesClient({
|
|
73
|
+
restEndpoint: argv.clientUrl
|
|
74
|
+
});
|
|
75
|
+
const namespace = argv.n || argv.namespace || getCurrentNamespace();
|
|
76
|
+
const podName = argv._?.[0] || await promptPodName(prompter, argv, namespace, client);
|
|
77
|
+
if (!podName) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
let containerName = argv.c || argv.container;
|
|
81
|
+
if (!containerName) {
|
|
82
|
+
containerName = await promptContainerName(prompter, argv, namespace, podName, client);
|
|
83
|
+
if (!containerName) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
console.log(chalk.blue(`Getting logs for ${containerName ? 'container ' + containerName + ' in ' : ''}pod ${podName} in namespace ${namespace}...`));
|
|
88
|
+
const logs = await client.readCoreV1NamespacedPodLog({
|
|
89
|
+
path: {
|
|
90
|
+
namespace,
|
|
91
|
+
name: podName
|
|
92
|
+
},
|
|
93
|
+
query: {
|
|
94
|
+
container: containerName,
|
|
95
|
+
tailLines: argv.tail ? parseInt(argv.tail, 10) : undefined,
|
|
96
|
+
follow: false,
|
|
97
|
+
previous: argv.previous === true
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
console.log(logs);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
console.error(chalk.red(`Error: ${error}`));
|
|
104
|
+
}
|
|
105
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { KubernetesClient } from 'kubernetesjs';
|
|
3
|
+
import { getCurrentNamespace } from '../config';
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
async function promptServiceName(prompter, argv, namespace, client) {
|
|
6
|
+
try {
|
|
7
|
+
const services = await client.listCoreV1NamespacedService({
|
|
8
|
+
path: { namespace },
|
|
9
|
+
query: { limit: 100 }
|
|
10
|
+
});
|
|
11
|
+
if (!services.items || services.items.length === 0) {
|
|
12
|
+
console.log(chalk.yellow(`No services found in namespace ${namespace}`));
|
|
13
|
+
return '';
|
|
14
|
+
}
|
|
15
|
+
const options = services.items.map(service => ({
|
|
16
|
+
name: service.metadata.name,
|
|
17
|
+
value: service.metadata.name
|
|
18
|
+
}));
|
|
19
|
+
const question = {
|
|
20
|
+
type: 'autocomplete',
|
|
21
|
+
name: 'serviceName',
|
|
22
|
+
message: 'Select service',
|
|
23
|
+
options,
|
|
24
|
+
maxDisplayLines: 10,
|
|
25
|
+
required: true
|
|
26
|
+
};
|
|
27
|
+
const { serviceName } = await prompter.prompt(argv, [question]);
|
|
28
|
+
return serviceName;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
console.error(chalk.red(`Error getting services: ${error}`));
|
|
32
|
+
return '';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function promptPortMapping(prompter, argv, namespace, serviceName, client) {
|
|
36
|
+
try {
|
|
37
|
+
const service = await client.readCoreV1NamespacedService({
|
|
38
|
+
path: {
|
|
39
|
+
namespace,
|
|
40
|
+
name: serviceName
|
|
41
|
+
},
|
|
42
|
+
query: {}
|
|
43
|
+
});
|
|
44
|
+
if (!service.spec || !service.spec.ports || service.spec.ports.length === 0) {
|
|
45
|
+
console.log(chalk.yellow(`No ports found in service ${serviceName}`));
|
|
46
|
+
return '';
|
|
47
|
+
}
|
|
48
|
+
if (service.spec.ports.length === 1) {
|
|
49
|
+
const port = service.spec.ports[0];
|
|
50
|
+
const localPort = port.port;
|
|
51
|
+
const remotePort = port.targetPort || port.port;
|
|
52
|
+
const confirmQuestion = {
|
|
53
|
+
type: 'confirm',
|
|
54
|
+
name: 'confirmPortMapping',
|
|
55
|
+
message: `Use port mapping ${localPort}:${remotePort}?`,
|
|
56
|
+
required: true
|
|
57
|
+
};
|
|
58
|
+
const { confirmPortMapping } = await prompter.prompt(argv, [confirmQuestion]);
|
|
59
|
+
if (confirmPortMapping) {
|
|
60
|
+
return `${localPort}:${remotePort}`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const portMappingQuestion = {
|
|
64
|
+
type: 'text',
|
|
65
|
+
name: 'portMapping',
|
|
66
|
+
message: 'Enter port mapping (local:remote)',
|
|
67
|
+
required: true
|
|
68
|
+
};
|
|
69
|
+
const { portMapping } = await prompter.prompt(argv, [portMappingQuestion]);
|
|
70
|
+
return portMapping;
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
console.error(chalk.red(`Error getting service ports: ${error}`));
|
|
74
|
+
return '';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async function portForward(namespace, resourceType, resourceName, portMapping) {
|
|
78
|
+
console.log(chalk.blue(`Forwarding ports ${portMapping} to ${resourceType}/${resourceName} in namespace ${namespace}...`));
|
|
79
|
+
console.log(chalk.yellow('Press Ctrl+C to stop port forwarding'));
|
|
80
|
+
const kubectlArgs = [
|
|
81
|
+
'port-forward',
|
|
82
|
+
'-n', namespace,
|
|
83
|
+
`${resourceType}/${resourceName}`,
|
|
84
|
+
portMapping
|
|
85
|
+
];
|
|
86
|
+
const kubectl = spawn('kubectl', kubectlArgs, {
|
|
87
|
+
stdio: 'inherit',
|
|
88
|
+
shell: true
|
|
89
|
+
});
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
kubectl.on('close', (code) => {
|
|
92
|
+
if (code === 0 || code === 130) { // 130 is the exit code when terminated by Ctrl+C
|
|
93
|
+
resolve();
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
reject(new Error(`kubectl port-forward exited with code ${code}`));
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
kubectl.on('error', (error) => {
|
|
100
|
+
reject(error);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
export default async (argv, prompter, _options) => {
|
|
105
|
+
try {
|
|
106
|
+
const client = new KubernetesClient({
|
|
107
|
+
restEndpoint: argv.clientUrl
|
|
108
|
+
});
|
|
109
|
+
const namespace = argv.n || argv.namespace || getCurrentNamespace();
|
|
110
|
+
let resourceType = 'svc';
|
|
111
|
+
let resourceName = '';
|
|
112
|
+
if (argv._?.[0]) {
|
|
113
|
+
const resourceArg = argv._[0];
|
|
114
|
+
if (resourceArg.includes('/')) {
|
|
115
|
+
const [type, name] = resourceArg.split('/');
|
|
116
|
+
resourceType = type;
|
|
117
|
+
resourceName = name;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
resourceName = resourceArg;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (!resourceName) {
|
|
124
|
+
resourceName = await promptServiceName(prompter, argv, namespace, client);
|
|
125
|
+
if (!resourceName) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const portMapping = argv._?.[1] || await promptPortMapping(prompter, argv, namespace, resourceName, client);
|
|
130
|
+
if (!portMapping) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
await portForward(namespace, resourceType, resourceName, portMapping);
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
console.error(chalk.red(`Error: ${error}`));
|
|
137
|
+
}
|
|
138
|
+
};
|
package/esm/commands.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { readAndParsePackageJson } from './package';
|
|
2
|
+
import { extractFirst, usageText } from './utils';
|
|
3
|
+
// Commands
|
|
4
|
+
import deploy from './commands/deploy';
|
|
5
|
+
import get from './commands/get';
|
|
6
|
+
import describe from './commands/describe';
|
|
7
|
+
import logs from './commands/logs';
|
|
8
|
+
import apply from './commands/apply';
|
|
9
|
+
import deleteCmd from './commands/delete';
|
|
10
|
+
import exec from './commands/exec';
|
|
11
|
+
import portForward from './commands/port-forward';
|
|
12
|
+
import clusterInfo from './commands/cluster-info';
|
|
13
|
+
import config from './commands/config';
|
|
14
|
+
const commandMap = {
|
|
15
|
+
deploy,
|
|
16
|
+
get,
|
|
17
|
+
describe,
|
|
18
|
+
logs,
|
|
19
|
+
apply,
|
|
20
|
+
delete: deleteCmd,
|
|
21
|
+
exec,
|
|
22
|
+
'port-forward': portForward,
|
|
23
|
+
'cluster-info': clusterInfo,
|
|
24
|
+
config
|
|
25
|
+
};
|
|
26
|
+
import configHandler from './commands/config-handler';
|
|
27
|
+
export const commands = async (argv, prompter, options) => {
|
|
28
|
+
if (argv.version || argv.v) {
|
|
29
|
+
const pkg = readAndParsePackageJson();
|
|
30
|
+
console.log(pkg.version);
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
if (argv.config) {
|
|
34
|
+
const handled = await configHandler(argv, prompter, options, commandMap);
|
|
35
|
+
if (handled) {
|
|
36
|
+
prompter.close();
|
|
37
|
+
return argv;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
let { first: command, newArgv } = extractFirst(argv);
|
|
41
|
+
// Show usage if explicitly requested
|
|
42
|
+
if (argv.help || argv.h || command === 'help') {
|
|
43
|
+
console.log(usageText);
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
// Prompt if no command provided
|
|
47
|
+
if (!command) {
|
|
48
|
+
const answer = await prompter.prompt(argv, [
|
|
49
|
+
{
|
|
50
|
+
type: 'autocomplete',
|
|
51
|
+
name: 'command',
|
|
52
|
+
message: 'What do you want to do?',
|
|
53
|
+
options: Object.keys(commandMap)
|
|
54
|
+
}
|
|
55
|
+
]);
|
|
56
|
+
command = answer.command;
|
|
57
|
+
}
|
|
58
|
+
// Prompt for working directory and client URL
|
|
59
|
+
newArgv = await prompter.prompt(newArgv, [
|
|
60
|
+
{
|
|
61
|
+
type: 'text',
|
|
62
|
+
name: 'cwd',
|
|
63
|
+
message: 'Working directory',
|
|
64
|
+
required: false,
|
|
65
|
+
default: process.cwd(),
|
|
66
|
+
useDefault: true
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
type: 'text',
|
|
70
|
+
name: 'clientUrl',
|
|
71
|
+
message: 'Kubernetes API URL',
|
|
72
|
+
required: false,
|
|
73
|
+
default: 'http://127.0.0.1:8001',
|
|
74
|
+
useDefault: true
|
|
75
|
+
}
|
|
76
|
+
]);
|
|
77
|
+
const commandFn = commandMap[command];
|
|
78
|
+
if (!commandFn) {
|
|
79
|
+
console.error(`Unknown command: ${command}`);
|
|
80
|
+
console.log(usageText);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
await commandFn(newArgv, prompter, options);
|
|
84
|
+
prompter.close();
|
|
85
|
+
return argv;
|
|
86
|
+
};
|
package/esm/config.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import * as yaml from 'js-yaml';
|
|
5
|
+
const KUBECONFIG_PATH = join(homedir(), '.kubeconfig');
|
|
6
|
+
const DEFAULT_NAMESPACE = 'default';
|
|
7
|
+
/**
|
|
8
|
+
* Read and parse a YAML file
|
|
9
|
+
* @param filePath Path to the YAML file
|
|
10
|
+
* @returns Parsed YAML content
|
|
11
|
+
*/
|
|
12
|
+
export function readYamlFile(filePath) {
|
|
13
|
+
try {
|
|
14
|
+
const fileContent = readFileSync(filePath, 'utf8');
|
|
15
|
+
return yaml.load(fileContent);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
console.error(`Error reading YAML file: ${error}`);
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Infer the resource type from a Kubernetes YAML
|
|
24
|
+
* @param resource The parsed Kubernetes resource
|
|
25
|
+
* @returns The resource type (lowercase)
|
|
26
|
+
*/
|
|
27
|
+
export function inferResourceType(resource) {
|
|
28
|
+
if (!resource || !resource.kind) {
|
|
29
|
+
throw new Error('Invalid Kubernetes resource: missing kind');
|
|
30
|
+
}
|
|
31
|
+
return resource.kind.toLowerCase();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Get the current namespace from the local kubeconfig
|
|
35
|
+
* @returns The current namespace
|
|
36
|
+
*/
|
|
37
|
+
export function getCurrentNamespace() {
|
|
38
|
+
try {
|
|
39
|
+
if (!existsSync(KUBECONFIG_PATH)) {
|
|
40
|
+
return DEFAULT_NAMESPACE;
|
|
41
|
+
}
|
|
42
|
+
const configContent = readFileSync(KUBECONFIG_PATH, 'utf8');
|
|
43
|
+
const config = JSON.parse(configContent);
|
|
44
|
+
return config.currentNamespace || DEFAULT_NAMESPACE;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
console.error(`Error reading kubeconfig: ${error}`);
|
|
48
|
+
return DEFAULT_NAMESPACE;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Set the current namespace in the local kubeconfig
|
|
53
|
+
* @param namespace The namespace to set
|
|
54
|
+
*/
|
|
55
|
+
export function setCurrentNamespace(namespace) {
|
|
56
|
+
try {
|
|
57
|
+
let config = { currentNamespace: namespace };
|
|
58
|
+
if (existsSync(KUBECONFIG_PATH)) {
|
|
59
|
+
try {
|
|
60
|
+
const configContent = readFileSync(KUBECONFIG_PATH, 'utf8');
|
|
61
|
+
config = { ...JSON.parse(configContent), currentNamespace: namespace };
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
console.error(`Error parsing existing kubeconfig: ${error}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
writeFileSync(KUBECONFIG_PATH, JSON.stringify(config, null, 2), 'utf8');
|
|
68
|
+
console.log(`Namespace set to "${namespace}"`);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
console.error(`Error setting namespace: ${error}`);
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
package/esm/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { CLI } from 'inquirerer';
|
|
3
|
+
import { commands } from './commands';
|
|
4
|
+
export const options = {
|
|
5
|
+
minimistOpts: {
|
|
6
|
+
alias: {
|
|
7
|
+
v: 'version',
|
|
8
|
+
h: 'help',
|
|
9
|
+
c: 'clientUrl'
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const app = new CLI(commands, options);
|
|
14
|
+
app.run().then(() => {
|
|
15
|
+
// all done!
|
|
16
|
+
}).catch(error => {
|
|
17
|
+
console.error(error);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
});
|
package/esm/package.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { dirname, join } from 'path';
|
|
3
|
+
// need to search due to the dist/ folder and src/, etc.
|
|
4
|
+
function findPackageJson(currentDir) {
|
|
5
|
+
const filePath = join(currentDir, 'package.json');
|
|
6
|
+
// Check if package.json exists in the current directory
|
|
7
|
+
if (existsSync(filePath)) {
|
|
8
|
+
return filePath;
|
|
9
|
+
}
|
|
10
|
+
// Get the parent directory
|
|
11
|
+
const parentDir = dirname(currentDir);
|
|
12
|
+
// If reached the root directory, package.json is not found
|
|
13
|
+
if (parentDir === currentDir) {
|
|
14
|
+
throw new Error('package.json not found in any parent directory');
|
|
15
|
+
}
|
|
16
|
+
// Recursively look in the parent directory
|
|
17
|
+
return findPackageJson(parentDir);
|
|
18
|
+
}
|
|
19
|
+
export function readAndParsePackageJson() {
|
|
20
|
+
// Start searching from the current directory
|
|
21
|
+
const pkgPath = findPackageJson(__dirname);
|
|
22
|
+
// Read and parse the package.json
|
|
23
|
+
const str = readFileSync(pkgPath, 'utf8');
|
|
24
|
+
const pkg = JSON.parse(str);
|
|
25
|
+
return pkg;
|
|
26
|
+
}
|
package/esm/utils.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { readAndParsePackageJson } from './package';
|
|
3
|
+
export const extractFirst = (argv) => {
|
|
4
|
+
const first = argv._?.[0];
|
|
5
|
+
const newArgv = {
|
|
6
|
+
...argv,
|
|
7
|
+
_: argv._?.slice(1) ?? []
|
|
8
|
+
};
|
|
9
|
+
return { first, newArgv };
|
|
10
|
+
};
|
|
11
|
+
// Function to display the version information
|
|
12
|
+
export function displayVersion() {
|
|
13
|
+
const pkg = readAndParsePackageJson();
|
|
14
|
+
console.log(chalk.green(`Name: ${pkg.name}`));
|
|
15
|
+
console.log(chalk.blue(`Version: ${pkg.version}`));
|
|
16
|
+
}
|
|
17
|
+
export const usageText = `
|
|
18
|
+
Usage: k8s <command> [options]
|
|
19
|
+
|
|
20
|
+
Commands:
|
|
21
|
+
get [resource] List resources (pods, services, deployments, etc.)
|
|
22
|
+
describe [resource] Show detailed information about a specific resource
|
|
23
|
+
logs [pod] Display logs from a container in a pod
|
|
24
|
+
exec [pod] -- [cmd] Execute a command in a container
|
|
25
|
+
apply -f [file] Apply a configuration to a resource by file name
|
|
26
|
+
delete [resource] Delete resources
|
|
27
|
+
port-forward [svc] Forward one or more local ports to a pod
|
|
28
|
+
cluster-info Display cluster information
|
|
29
|
+
config Modify kubeconfig files
|
|
30
|
+
deploy Deploy a container to Kubernetes
|
|
31
|
+
version, -v Display the version of the CLI
|
|
32
|
+
|
|
33
|
+
Configuration File:
|
|
34
|
+
--config <path> Specify the path to a YAML configuration file.
|
|
35
|
+
Command-line options will override settings from this file if both are provided.
|
|
36
|
+
|
|
37
|
+
Namespace:
|
|
38
|
+
-n, --namespace Specify the namespace to use. Default is from local config or "default".
|
|
39
|
+
|
|
40
|
+
Additional Options:
|
|
41
|
+
-c, --clientUrl <url> Specify the Kubernetes API endpoint URL.
|
|
42
|
+
Default is http://127.0.0.1:8001 (kubectl proxy)
|
|
43
|
+
|
|
44
|
+
Additional Help:
|
|
45
|
+
$ k8s help Display this help information.
|
|
46
|
+
`;
|
|
47
|
+
export function displayUsage() {
|
|
48
|
+
console.log(usageText);
|
|
49
|
+
}
|
package/index.d.ts
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.options = void 0;
|
|
5
|
+
const inquirerer_1 = require("inquirerer");
|
|
6
|
+
const commands_1 = require("./commands");
|
|
7
|
+
exports.options = {
|
|
8
|
+
minimistOpts: {
|
|
9
|
+
alias: {
|
|
10
|
+
v: 'version',
|
|
11
|
+
h: 'help',
|
|
12
|
+
c: 'clientUrl'
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
const app = new inquirerer_1.CLI(commands_1.commands, exports.options);
|
|
17
|
+
app.run().then(() => {
|
|
18
|
+
// all done!
|
|
19
|
+
}).catch(error => {
|
|
20
|
+
console.error(error);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
});
|
package/package.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function readAndParsePackageJson(): any;
|