mcp-server-kubernetes 2.4.7 → 2.5.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.
@@ -1,8 +1,9 @@
1
- import { execSync } from "child_process";
1
+ import { execFileSync } from "child_process";
2
2
  import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
3
3
  import * as fs from "fs";
4
4
  import * as path from "path";
5
5
  import * as os from "os";
6
+ import { getSpawnMaxBuffer } from "../config/max-buffer.js";
6
7
  export const kubectlCreateSchema = {
7
8
  name: "kubectl_create",
8
9
  description: "Create Kubernetes resources using various methods (from file or using subcommands)",
@@ -13,53 +14,64 @@ export const kubectlCreateSchema = {
13
14
  dryRun: {
14
15
  type: "boolean",
15
16
  description: "If true, only validate the resource, don't actually create it",
16
- default: false
17
+ default: false,
17
18
  },
18
19
  output: {
19
20
  type: "string",
20
- enum: ["json", "yaml", "name", "go-template", "go-template-file", "template", "templatefile", "jsonpath", "jsonpath-as-json", "jsonpath-file"],
21
+ enum: [
22
+ "json",
23
+ "yaml",
24
+ "name",
25
+ "go-template",
26
+ "go-template-file",
27
+ "template",
28
+ "templatefile",
29
+ "jsonpath",
30
+ "jsonpath-as-json",
31
+ "jsonpath-file",
32
+ ],
21
33
  description: "Output format. One of: json|yaml|name|go-template|go-template-file|template|templatefile|jsonpath|jsonpath-as-json|jsonpath-file",
22
- default: "yaml"
34
+ default: "yaml",
23
35
  },
24
36
  validate: {
25
37
  type: "boolean",
26
38
  description: "If true, validate resource schema against server schema",
27
- default: true
39
+ default: true,
28
40
  },
29
41
  // Create from file method
30
42
  manifest: {
31
43
  type: "string",
32
- description: "YAML manifest to create resources from"
44
+ description: "YAML manifest to create resources from",
33
45
  },
34
46
  filename: {
35
47
  type: "string",
36
- description: "Path to a YAML file to create resources from"
48
+ description: "Path to a YAML file to create resources from",
37
49
  },
38
50
  // Resource type to create (determines which subcommand to use)
39
51
  resourceType: {
40
52
  type: "string",
41
- description: "Type of resource to create (namespace, configmap, deployment, service, etc.)"
53
+ description: "Type of resource to create (namespace, configmap, deployment, service, etc.)",
42
54
  },
43
55
  // Common parameters for most resource types
44
56
  name: {
45
57
  type: "string",
46
- description: "Name of the resource to create"
58
+ description: "Name of the resource to create",
47
59
  },
48
60
  namespace: {
49
61
  type: "string",
50
62
  description: "Namespace to create the resource in",
51
- default: "default"
63
+ default: "default",
52
64
  },
53
65
  // ConfigMap specific parameters
54
66
  fromLiteral: {
55
67
  type: "array",
56
68
  items: { type: "string" },
57
- description: "Key-value pair for creating configmap (e.g. [\"key1=value1\", \"key2=value2\"])"
69
+ description: 'Key-value pair for creating configmap (e.g. ["key1=value1", "key2=value2"])',
58
70
  },
59
71
  fromFile: {
60
72
  type: "array",
61
73
  items: { type: "string" },
62
- description: "Path to file for creating configmap (e.g. [\"key1=/path/to/file1\", \"key2=/path/to/file2\"])"
74
+ description: 'Path to file for creating configmap (e.g. ["key1=/path/to/file1", "key2=/path/to/file2"])',
63
75
  },
64
76
  // Namespace specific parameters
65
77
  // No special parameters for namespace, just name is needed
@@ -67,60 +79,60 @@ export const kubectlCreateSchema = {
67
79
  secretType: {
68
80
  type: "string",
69
81
  enum: ["generic", "docker-registry", "tls"],
70
- description: "Type of secret to create (generic, docker-registry, tls)"
82
+ description: "Type of secret to create (generic, docker-registry, tls)",
71
83
  },
72
84
  // Service specific parameters
73
85
  serviceType: {
74
86
  type: "string",
75
87
  enum: ["clusterip", "nodeport", "loadbalancer", "externalname"],
76
- description: "Type of service to create (clusterip, nodeport, loadbalancer, externalname)"
88
+ description: "Type of service to create (clusterip, nodeport, loadbalancer, externalname)",
77
89
  },
78
90
  tcpPort: {
79
91
  type: "array",
80
92
  items: { type: "string" },
81
- description: "Port pairs for tcp service (e.g. [\"80:8080\", \"443:8443\"])"
93
+ description: 'Port pairs for tcp service (e.g. ["80:8080", "443:8443"])',
82
94
  },
83
95
  // Deployment specific parameters
84
96
  image: {
85
97
  type: "string",
86
- description: "Image to use for the containers in the deployment"
98
+ description: "Image to use for the containers in the deployment",
87
99
  },
88
100
  replicas: {
89
101
  type: "number",
90
102
  description: "Number of replicas to create for the deployment",
91
- default: 1
103
+ default: 1,
92
104
  },
93
105
  port: {
94
106
  type: "number",
95
- description: "Port that the container exposes"
107
+ description: "Port that the container exposes",
96
108
  },
97
109
  // CronJob specific parameters
98
110
  schedule: {
99
111
  type: "string",
100
- description: "Cron schedule expression for the CronJob (e.g. \"*/5 * * * *\")"
112
+ description: 'Cron schedule expression for the CronJob (e.g. "*/5 * * * *")',
101
113
  },
102
114
  suspend: {
103
115
  type: "boolean",
104
116
  description: "Whether to suspend the CronJob",
105
- default: false
117
+ default: false,
106
118
  },
107
119
  // Job specific parameters
108
120
  command: {
109
121
  type: "array",
110
122
  items: { type: "string" },
111
- description: "Command to run in the container"
123
+ description: "Command to run in the container",
112
124
  },
113
125
  // Additional common parameters
114
126
  labels: {
115
127
  type: "array",
116
128
  items: { type: "string" },
117
- description: "Labels to apply to the resource (e.g. [\"key1=value1\", \"key2=value2\"])"
129
+ description: 'Labels to apply to the resource (e.g. ["key1=value1", "key2=value2"])',
118
130
  },
119
131
  annotations: {
120
132
  type: "array",
121
133
  items: { type: "string" },
122
- description: "Annotations to apply to the resource (e.g. [\"key1=value1\", \"key2=value2\"])"
123
- }
134
+ description: 'Annotations to apply to the resource (e.g. ["key1=value1", "key2=value2"])',
135
+ },
124
136
  },
125
137
  required: [],
126
138
  },
@@ -132,7 +144,9 @@ export async function kubectlCreate(k8sManager, input) {
132
144
  throw new McpError(ErrorCode.InvalidRequest, "Either manifest, filename, or resourceType must be provided");
133
145
  }
134
146
  // If resourceType is provided, check if name is provided for most resource types
135
- if (input.resourceType && !input.name && input.resourceType !== "namespace") {
147
+ if (input.resourceType &&
148
+ !input.name &&
149
+ input.resourceType !== "namespace") {
136
150
  throw new McpError(ErrorCode.InvalidRequest, `Name is required when creating a ${input.resourceType}`);
137
151
  }
138
152
  // Set up common parameters
@@ -140,7 +154,8 @@ export async function kubectlCreate(k8sManager, input) {
140
154
  const dryRun = input.dryRun || false;
141
155
  const validate = input.validate ?? true;
142
156
  const output = input.output || "yaml";
143
- let command = "kubectl create";
157
+ const command = "kubectl";
158
+ const args = ["create"];
144
159
  let tempFile = null;
145
160
  // Process manifest content if provided (file-based creation)
146
161
  if (input.manifest || input.filename) {
@@ -149,30 +164,30 @@ export async function kubectlCreate(k8sManager, input) {
149
164
  const tmpDir = os.tmpdir();
150
165
  tempFile = path.join(tmpDir, `create-manifest-${Date.now()}.yaml`);
151
166
  fs.writeFileSync(tempFile, input.manifest);
152
- command += ` -f ${tempFile}`;
167
+ args.push("-f", tempFile);
153
168
  }
154
169
  else if (input.filename) {
155
- command += ` -f ${input.filename}`;
170
+ args.push("-f", input.filename);
156
171
  }
157
172
  }
158
173
  else {
159
174
  // Process subcommand-based creation
160
175
  switch (input.resourceType?.toLowerCase()) {
161
176
  case "namespace":
162
- command += ` namespace ${input.name}`;
177
+ args.push("namespace", input.name);
163
178
  break;
164
179
  case "configmap":
165
- command += ` configmap ${input.name}`;
180
+ args.push("configmap", input.name);
166
181
  // Add --from-literal arguments
167
182
  if (input.fromLiteral && input.fromLiteral.length > 0) {
168
- input.fromLiteral.forEach(literal => {
169
- command += ` --from-literal=${literal}`;
183
+ input.fromLiteral.forEach((literal) => {
184
+ args.push(`--from-literal=${literal}`);
170
185
  });
171
186
  }
172
187
  // Add --from-file arguments
173
188
  if (input.fromFile && input.fromFile.length > 0) {
174
- input.fromFile.forEach(file => {
175
- command += ` --from-file=${file}`;
189
+ input.fromFile.forEach((file) => {
190
+ args.push(`--from-file=${file}`);
176
191
  });
177
192
  }
178
193
  break;
@@ -180,17 +195,17 @@ export async function kubectlCreate(k8sManager, input) {
180
195
  if (!input.secretType) {
181
196
  throw new McpError(ErrorCode.InvalidRequest, "secretType is required when creating a secret");
182
197
  }
183
- command += ` secret ${input.secretType} ${input.name}`;
198
+ args.push("secret", input.secretType, input.name);
184
199
  // Add --from-literal arguments
185
200
  if (input.fromLiteral && input.fromLiteral.length > 0) {
186
- input.fromLiteral.forEach(literal => {
187
- command += ` --from-literal=${literal}`;
201
+ input.fromLiteral.forEach((literal) => {
202
+ args.push(`--from-literal=${literal}`);
188
203
  });
189
204
  }
190
205
  // Add --from-file arguments
191
206
  if (input.fromFile && input.fromFile.length > 0) {
192
- input.fromFile.forEach(file => {
193
- command += ` --from-file=${file}`;
207
+ input.fromFile.forEach((file) => {
208
+ args.push(`--from-file=${file}`);
194
209
  });
195
210
  }
196
211
  break;
@@ -199,11 +214,11 @@ export async function kubectlCreate(k8sManager, input) {
199
214
  // Default to clusterip if not specified
200
215
  input.serviceType = "clusterip";
201
216
  }
202
- command += ` service ${input.serviceType} ${input.name}`;
217
+ args.push("service", input.serviceType, input.name);
203
218
  // Add --tcp arguments for ports
204
219
  if (input.tcpPort && input.tcpPort.length > 0) {
205
- input.tcpPort.forEach(port => {
206
- command += ` --tcp=${port}`;
220
+ input.tcpPort.forEach((port) => {
221
+ args.push(`--tcp=${port}`);
207
222
  });
208
223
  }
209
224
  break;
@@ -214,38 +229,38 @@ export async function kubectlCreate(k8sManager, input) {
214
229
  if (!input.schedule) {
215
230
  throw new McpError(ErrorCode.InvalidRequest, "schedule is required when creating a cronjob");
216
231
  }
217
- command += ` cronjob ${input.name} --image=${input.image} --schedule="${input.schedule}"`;
232
+ args.push("cronjob", input.name, `--image=${input.image}`, `--schedule=${input.schedule}`);
218
233
  // Add command if specified
219
234
  if (input.command && input.command.length > 0) {
220
- command += ` -- ${input.command.join(" ")}`;
235
+ args.push("--", ...input.command);
221
236
  }
222
237
  // Add suspend flag if specified
223
238
  if (input.suspend === true) {
224
- command += ` --suspend`;
239
+ args.push(`--suspend`);
225
240
  }
226
241
  break;
227
242
  case "deployment":
228
243
  if (!input.image) {
229
244
  throw new McpError(ErrorCode.InvalidRequest, "image is required when creating a deployment");
230
245
  }
231
- command += ` deployment ${input.name} --image=${input.image}`;
246
+ args.push("deployment", input.name, `--image=${input.image}`);
232
247
  // Add replicas if specified
233
248
  if (input.replicas) {
234
- command += ` --replicas=${input.replicas}`;
249
+ args.push(`--replicas=${input.replicas}`);
235
250
  }
236
251
  // Add port if specified
237
252
  if (input.port) {
238
- command += ` --port=${input.port}`;
253
+ args.push(`--port=${input.port}`);
239
254
  }
240
255
  break;
241
256
  case "job":
242
257
  if (!input.image) {
243
258
  throw new McpError(ErrorCode.InvalidRequest, "image is required when creating a job");
244
259
  }
245
- command += ` job ${input.name} --image=${input.image}`;
260
+ args.push("job", input.name, `--image=${input.image}`);
246
261
  // Add command if specified
247
262
  if (input.command && input.command.length > 0) {
248
- command += ` -- ${input.command.join(" ")}`;
263
+ args.push("--", ...input.command);
249
264
  }
250
265
  break;
251
266
  default:
@@ -254,33 +269,37 @@ export async function kubectlCreate(k8sManager, input) {
254
269
  }
255
270
  // Add namespace if not creating a namespace itself
256
271
  if (input.resourceType !== "namespace") {
257
- command += ` -n ${namespace}`;
272
+ args.push("-n", namespace);
258
273
  }
259
274
  // Add labels if specified
260
275
  if (input.labels && input.labels.length > 0) {
261
- input.labels.forEach(label => {
262
- command += ` -l ${label}`;
276
+ input.labels.forEach((label) => {
277
+ args.push("-l", label);
263
278
  });
264
279
  }
265
280
  // Add annotations if specified
266
281
  if (input.annotations && input.annotations.length > 0) {
267
- input.annotations.forEach(annotation => {
268
- command += ` --annotation=${annotation}`;
282
+ input.annotations.forEach((annotation) => {
283
+ args.push(`--annotation=${annotation}`);
269
284
  });
270
285
  }
271
286
  // Add dry-run flag if requested
272
287
  if (dryRun) {
273
- command += " --dry-run=client";
288
+ args.push("--dry-run=client");
274
289
  }
275
290
  // Add validate flag if needed
276
291
  if (!validate) {
277
- command += " --validate=false";
292
+ args.push("--validate=false");
278
293
  }
279
294
  // Add output format
280
- command += ` -o ${output}`;
295
+ args.push("-o", output);
281
296
  // Execute the command
282
297
  try {
283
- const result = execSync(command, { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG } });
298
+ const result = execFileSync(command, args, {
299
+ encoding: "utf8",
300
+ maxBuffer: getSpawnMaxBuffer(),
301
+ env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG },
302
+ });
284
303
  // Clean up temp file if created
285
304
  if (tempFile) {
286
305
  try {
@@ -1,8 +1,9 @@
1
- import { execSync } from "child_process";
1
+ import { execFileSync } from "child_process";
2
2
  import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
3
3
  import * as fs from "fs";
4
4
  import * as path from "path";
5
5
  import * as os from "os";
6
+ import { getSpawnMaxBuffer } from "../config/max-buffer.js";
6
7
  export const kubectlDeleteSchema = {
7
8
  name: "kubectl_delete",
8
9
  description: "Delete Kubernetes resources by resource type, name, labels, or from a manifest file",
@@ -11,43 +12,43 @@ export const kubectlDeleteSchema = {
11
12
  properties: {
12
13
  resourceType: {
13
14
  type: "string",
14
- description: "Type of resource to delete (e.g., pods, deployments, services, etc.)"
15
+ description: "Type of resource to delete (e.g., pods, deployments, services, etc.)",
15
16
  },
16
17
  name: {
17
18
  type: "string",
18
- description: "Name of the resource to delete"
19
+ description: "Name of the resource to delete",
19
20
  },
20
21
  namespace: {
21
22
  type: "string",
22
23
  description: "Namespace of the resource (optional - defaults to 'default' for namespaced resources)",
23
- default: "default"
24
+ default: "default",
24
25
  },
25
26
  labelSelector: {
26
27
  type: "string",
27
- description: "Delete resources matching this label selector (e.g. 'app=nginx')"
28
+ description: "Delete resources matching this label selector (e.g. 'app=nginx')",
28
29
  },
29
30
  manifest: {
30
31
  type: "string",
31
- description: "YAML manifest defining resources to delete (optional)"
32
+ description: "YAML manifest defining resources to delete (optional)",
32
33
  },
33
34
  filename: {
34
35
  type: "string",
35
- description: "Path to a YAML file to delete resources from (optional)"
36
+ description: "Path to a YAML file to delete resources from (optional)",
36
37
  },
37
38
  allNamespaces: {
38
39
  type: "boolean",
39
40
  description: "If true, delete resources across all namespaces",
40
- default: false
41
+ default: false,
41
42
  },
42
43
  force: {
43
44
  type: "boolean",
44
45
  description: "If true, immediately remove resources from API and bypass graceful deletion",
45
- default: false
46
+ default: false,
46
47
  },
47
48
  gracePeriodSeconds: {
48
49
  type: "number",
49
- description: "Period of time in seconds given to the resource to terminate gracefully"
50
- }
50
+ description: "Period of time in seconds given to the resource to terminate gracefully",
51
+ },
51
52
  },
52
53
  required: ["resourceType", "name", "namespace"],
53
54
  },
@@ -65,7 +66,8 @@ export async function kubectlDelete(k8sManager, input) {
65
66
  const namespace = input.namespace || "default";
66
67
  const allNamespaces = input.allNamespaces || false;
67
68
  const force = input.force || false;
68
- let command = "kubectl delete";
69
+ const command = "kubectl";
70
+ const args = ["delete"];
69
71
  let tempFile = null;
70
72
  // Handle deleting from manifest or file
71
73
  if (input.manifest) {
@@ -73,39 +75,45 @@ export async function kubectlDelete(k8sManager, input) {
73
75
  const tmpDir = os.tmpdir();
74
76
  tempFile = path.join(tmpDir, `delete-manifest-${Date.now()}.yaml`);
75
77
  fs.writeFileSync(tempFile, input.manifest);
76
- command += ` -f ${tempFile}`;
78
+ args.push("-f", tempFile);
77
79
  }
78
80
  else if (input.filename) {
79
- command += ` -f ${input.filename}`;
81
+ args.push("-f", input.filename);
80
82
  }
81
83
  else {
82
84
  // Handle deleting by resource type and name/selector
83
- command += ` ${input.resourceType}`;
85
+ args.push(input.resourceType);
84
86
  if (input.name) {
85
- command += ` ${input.name}`;
87
+ args.push(input.name);
86
88
  }
87
89
  if (input.labelSelector) {
88
- command += ` -l ${input.labelSelector}`;
90
+ args.push("-l", input.labelSelector);
89
91
  }
90
92
  }
91
93
  // Add namespace flags
92
94
  if (allNamespaces) {
93
- command += " --all-namespaces";
95
+ args.push("--all-namespaces");
94
96
  }
95
- else if (namespace && input.resourceType && !isNonNamespacedResource(input.resourceType)) {
96
- command += ` -n ${namespace}`;
97
+ else if (namespace &&
98
+ input.resourceType &&
99
+ !isNonNamespacedResource(input.resourceType)) {
100
+ args.push("-n", namespace);
97
101
  }
98
102
  // Add force flag if requested
99
103
  if (force) {
100
- command += " --force";
104
+ args.push("--force");
101
105
  }
102
106
  // Add grace period if specified
103
107
  if (input.gracePeriodSeconds !== undefined) {
104
- command += ` --grace-period=${input.gracePeriodSeconds}`;
108
+ args.push(`--grace-period=${input.gracePeriodSeconds}`);
105
109
  }
106
110
  // Execute the command
107
111
  try {
108
- const result = execSync(command, { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG } });
112
+ const result = execFileSync(command, args, {
113
+ encoding: "utf8",
114
+ maxBuffer: getSpawnMaxBuffer(),
115
+ env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG },
116
+ });
109
117
  // Clean up temp file if created
110
118
  if (tempFile) {
111
119
  try {
@@ -161,13 +169,21 @@ export async function kubectlDelete(k8sManager, input) {
161
169
  // Helper function to determine if a resource is non-namespaced
162
170
  function isNonNamespacedResource(resourceType) {
163
171
  const nonNamespacedResources = [
164
- "nodes", "node", "no",
165
- "namespaces", "namespace", "ns",
166
- "persistentvolumes", "pv",
167
- "storageclasses", "sc",
172
+ "nodes",
173
+ "node",
174
+ "no",
175
+ "namespaces",
176
+ "namespace",
177
+ "ns",
178
+ "persistentvolumes",
179
+ "pv",
180
+ "storageclasses",
181
+ "sc",
168
182
  "clusterroles",
169
183
  "clusterrolebindings",
170
- "customresourcedefinitions", "crd", "crds"
184
+ "customresourcedefinitions",
185
+ "crd",
186
+ "crds",
171
187
  ];
172
188
  return nonNamespacedResources.includes(resourceType.toLowerCase());
173
189
  }
@@ -1,5 +1,6 @@
1
- import { execSync } from "child_process";
1
+ import { execFileSync } from "child_process";
2
2
  import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
3
+ import { getSpawnMaxBuffer } from "../config/max-buffer.js";
3
4
  export const kubectlDescribeSchema = {
4
5
  name: "kubectl_describe",
5
6
  description: "Describe Kubernetes resources by resource type, name, and optionally namespace",
@@ -8,22 +9,22 @@ export const kubectlDescribeSchema = {
8
9
  properties: {
9
10
  resourceType: {
10
11
  type: "string",
11
- description: "Type of resource to describe (e.g., pods, deployments, services, etc.)"
12
+ description: "Type of resource to describe (e.g., pods, deployments, services, etc.)",
12
13
  },
13
14
  name: {
14
15
  type: "string",
15
- description: "Name of the resource to describe"
16
+ description: "Name of the resource to describe",
16
17
  },
17
18
  namespace: {
18
19
  type: "string",
19
20
  description: "Namespace of the resource (optional - defaults to 'default' for namespaced resources)",
20
- default: "default"
21
+ default: "default",
21
22
  },
22
23
  allNamespaces: {
23
24
  type: "boolean",
24
25
  description: "If true, describe resources across all namespaces",
25
- default: false
26
- }
26
+ default: false,
27
+ },
27
28
  },
28
29
  required: ["resourceType", "name"],
29
30
  },
@@ -35,21 +36,22 @@ export async function kubectlDescribe(k8sManager, input) {
35
36
  const namespace = input.namespace || "default";
36
37
  const allNamespaces = input.allNamespaces || false;
37
38
  // Build the kubectl command
38
- let command = "kubectl describe ";
39
- // Add resource type
40
- command += resourceType;
41
- // Add name
42
- command += ` ${name}`;
39
+ const command = "kubectl";
40
+ const args = ["describe", resourceType, name];
43
41
  // Add namespace flag unless all namespaces is specified
44
42
  if (allNamespaces) {
45
- command += " --all-namespaces";
43
+ args.push("--all-namespaces");
46
44
  }
47
45
  else if (namespace && !isNonNamespacedResource(resourceType)) {
48
- command += ` -n ${namespace}`;
46
+ args.push("-n", namespace);
49
47
  }
50
48
  // Execute the command
51
49
  try {
52
- const result = execSync(command, { encoding: "utf8", env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG } });
50
+ const result = execFileSync(command, args, {
51
+ encoding: "utf8",
52
+ maxBuffer: getSpawnMaxBuffer(),
53
+ env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG },
54
+ });
53
55
  return {
54
56
  content: [
55
57
  {
@@ -84,13 +86,21 @@ export async function kubectlDescribe(k8sManager, input) {
84
86
  // Helper function to determine if a resource is non-namespaced
85
87
  function isNonNamespacedResource(resourceType) {
86
88
  const nonNamespacedResources = [
87
- "nodes", "node", "no",
88
- "namespaces", "namespace", "ns",
89
- "persistentvolumes", "pv",
90
- "storageclasses", "sc",
89
+ "nodes",
90
+ "node",
91
+ "no",
92
+ "namespaces",
93
+ "namespace",
94
+ "ns",
95
+ "persistentvolumes",
96
+ "pv",
97
+ "storageclasses",
98
+ "sc",
91
99
  "clusterroles",
92
100
  "clusterrolebindings",
93
- "customresourcedefinitions", "crd", "crds"
101
+ "customresourcedefinitions",
102
+ "crd",
103
+ "crds",
94
104
  ];
95
105
  return nonNamespacedResources.includes(resourceType.toLowerCase());
96
106
  }