-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclient.js
322 lines (289 loc) · 11 KB
/
client.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
// Third-party libs
const Kubernetes = require('kubernetes-client');
const K8sClient = Kubernetes.Client;
const K8sConfig = Kubernetes.config;
// Local libs
const APIMap = require('./apimap');
const Utils = require('./utils');
const Request = require('./request');
// Default namespace is used when a request for a namespaced resource is made but no namespace is provided
let defaultNamespace = 'default';
/**
* Creates a Kubernetes client which can be used to communicate with the
* Kubernetes master API
*
* @param {Object} kubeconfig Cluster's kubeconfig object
* @param {String} apiVersion Specify that the client should be for a particular application group
* and version. Useful to pre-build the request object past the version
* part.
* @return {Object} Promise
*/
module.exports.getKubernetesClient = async (kubeconfig, apiVersion) => {
const kubeconfigCopy = JSON.parse(JSON.stringify(kubeconfig));
const k8sLibConfig = K8sConfig.fromKubeconfig(kubeconfigCopy);
const client = new K8sClient({config: k8sLibConfig});
try {
await client.loadSpec();
} catch(err) {
// Try and add the status code as it's not provided
// Regex matches a colon, any number of spaces, and then 3 numbers before the end of the message
const rx = /(:[ ]*)([0-9]{3}$)/;
const errMsg = err.message;
const rxResults = rx.exec(errMsg);
if (rxResults) {
try {
const statusCode = parseInt(rxResults[2]);
err.statusCode = statusCode;
} catch (err) {
err.statusCode = 500;
}
} else {
err.statusCode = 500;
}
throw err;
}
if (apiVersion && typeof(apiVersion) === 'string') {
const verSplit = apiVersion.split('/');
if (verSplit.length === 1) {
return client.api[verSplit[0]];
}
else if (verSplit.length === 2) {
return client.apis[verSplit[0]][verSplit[1]];
} else {
const err = new Error(`Version string '${apiVersion}' is invalid, should be e.g. batch/v1beta1`);
err.statusCode = 400;
throw err;
}
}
return client;
};
/**
* Changes the default namespace to the one specified.
*
* @param {String} newDefault Namespace in the cluster to use by default
* @returns {void}
*/
module.exports.changeDefaultNamespace = (newDefault) => {
if (newDefault && typeof(newDefault) === 'string') defaultNamespace = newDefault;
};
/**
* Hits the version endpoint and retrieves metadata about the kubemaster.
*
* @param {Object} kubeconfig Kubeconfig object for the cluster
* @returns Promise
*/
module.exports.getVersion = (kubeconfig) => {
if (!kubeconfig || typeof(kubeconfig) !== 'object') {
const err = new Error('\'kubeconfig\' parameter must be an object');
err.status = 400;
throw err;
}
return Request.cluster(kubeconfig, 'get', '/version')
.then((res) => {
Request.parseStatus(res, new Error());
return res.body;
});
};
/**
* Gets a k8s lib request chain for the given resource type and name in the given namespace.
*
* @param {Object} kubeconfig Cluster's kubeconfig file
* @param {String} namespace (Optional) Namespace the resource is in, can be falsy to use the default
* namespace or 'all' to target all namespaces. Leave falsy if the
* resource is not namespaced at all.
* @param {String} resourceType Type of the resource, can be singular or plural
* @param {String} resourceName (Optional) Non-falsy to specify a specific resource name
* @returns K8s lib request chain ready for making request
*/
module.exports.buildChain = async (kubeconfig, namespace, resourceType, resourceName) => {
if (!kubeconfig || typeof(kubeconfig) !== 'object') {
const err = new Error('Missing kubeconfig, must be an object');
err.statusCode = 400;
throw err;
}
if (resourceType) resourceType = resourceType.toLowerCase();
// Cluster version defines which API group we'll use
const verResp = await this.getVersion(kubeconfig);
const clusterVer = Utils.prettifyVersion(verResp.gitVersion, 2);
// Get the API group for the resource
let apiVersion, namespaced;
if (resourceType && typeof(resourceType) === 'string') {
let apiGroupInfo = APIMap.getGroupInfo(clusterVer, resourceType);
if (!apiGroupInfo) {
await APIMap.buildAPIMap(kubeconfig);
}
apiGroupInfo = APIMap.getGroupInfo(clusterVer, resourceType);
if (!apiGroupInfo) {
const err = new Error(`Could not get API group info for resource '${resourceType}' in cluster with k8s version '${clusterVer}'`);
err.statusCode = 500;
throw err;
}
apiVersion = apiGroupInfo.version;
namespaced = apiGroupInfo.namespaced;
}
// Get the client to communicate with the cluster
const client = await this.getKubernetesClient(kubeconfig, apiVersion);
// Chain is built progressively to allow for flexible namespacing and resource naming
let reqChain = client;
if (namespaced && namespace !== 'all' && resourceType && apiVersion) {
if (!namespace || typeof(namespace) !== 'string') namespace = defaultNamespace;
reqChain = reqChain.namespaces(namespace);
}
reqChain = reqChain[resourceType];
if (resourceName && typeof(resourceName) === 'string' && resourceType && apiVersion) {
reqChain = reqChain(resourceName);
}
return reqChain;
};
/**
* Gets the desired resource from the cluster.
*
* @param {Object} kubeconfig Kubeconfig object with current-context pointing to context for correct cluster
* @param {String} namespace (Optional) Namespace the resource is in, can be falsy to use the default
* namespace or 'all' to target all namespaces. Leave falsy if the
* resource is not namespaced at all.
* @param {String} resourceType Type of the resource to retrieve, can be singular or plural
* @param {String} resourceName (Optional) Non-falsy to specify a specific resource to get
* @param {String} options (Optional) Pass options to get command
* @returns Promise
*/
module.exports.get = async (kubeconfig, namespace, resourceType, resourceName, options) => {
const reqChain = await this.buildChain(kubeconfig, namespace, resourceType, resourceName);
return reqChain.get(options);
};
/**
* Gets the desired logs from the cluster pod container.
*
* @param {Object} kubeconfig Kubeconfig object with current-context pointing to context for correct cluster
* @param {String} namespace (Optional) Namespace the resource is in, can be falsy to use the default
* namespace or 'all' to target all namespaces. Leave falsy if the
* resource is not namespaced at all.
* @param {String} resourceName (Optional) Non-falsy to specify a specific resource to get
* @param {String} options (Optional) Pass options to get command
* @returns Promise
*/
module.exports.logs = async (kubeconfig, namespace, resourceName, options) => {
const reqChain = await this.buildChain(kubeconfig, namespace, 'pod', resourceName);
return reqChain.log.get(options);
};
/**
* Deletes the given resource from the cluster.
*
* @param {Object} kubeconfig Kubeconfig object with current-context pointing to context for correct cluster
* @param {String} namespace (Optional) Namespace the resource is in, can be falsy to use the default
* namespace or 'all' to target all namespaces. Leave falsy if the
* resource is not namespaced at all.
* @param {String} resourceType Type of the resource to retrieve, can be singular or plural
* @param {String} resourceName (Optional) Non-falsy to specify a specific resource to get
* @param {String} options (Optional) Pass options to get command
* @returns Promise
*/
module.exports.delete = async (kubeconfig, namespace, resourceType, resourceName, options) => {
const reqChain = await this.buildChain(kubeconfig, namespace, resourceType, resourceName);
return reqChain.delete(options);
};
/**
* Updates or creates the given resource in the cluster.
*
* @param {Object} kubeconfig Kubeconfig object with current-context pointing to context for correct cluster
* @param {Object} resourceSpec Kubernetes specification file for the resource
* @returns Promise
*/
module.exports.updateOrCreate = async (kubeconfig, resourceSpec) => {
const namespace = resourceSpec.metadata.namespace;
const resourceType = resourceSpec.kind.toLowerCase();
const resourceName = resourceSpec.metadata.name;
const reqChain = await this.buildChain(kubeconfig, namespace, resourceType, resourceName);
return reqChain.patch({
headers: {
'content-type': 'application/merge-patch+json'
},
body: resourceSpec
}).catch((err) => {
if (err && typeof(err) === 'object' && err.code === 404) {
return reqChain.post({body: resourceSpec});
} else {
throw err;
}
});
};
/**
* Creates a spec for a Docker secret using the given repository and
* account information.
*
* @param {String} secretName Name of the docker secret
* @param {String} namespace Namespace the secret should be placed in
* @param {String} repositoryURL Endpoint for the Docker repository
* @param {String} dockerUsername Username for the repository account to use
* @param {String} dockerPassword Password for the repository account to use
* @returns JSON object containing a Docker secret spec
*/
module.exports.buildDockerSecret = (secretName, namespace, repositoryURL, dockerUsername, dockerPassword) => {
const rawAuthToken = `${dockerUsername}:${dockerPassword}`;
const b64AuthToken = new Buffer(rawAuthToken).toString('base64');
const authObj = {
auths: {}
};
authObj.auths[repositoryURL] = {
auth: b64AuthToken
};
const rawAuthString = JSON.stringify(authObj);
const b64AuthString = new Buffer(rawAuthString).toString('base64');
const secretJSON = {
kind: 'Secret',
metadata: {
name: secretName,
namespace: namespace || defaultNamespace
},
data: {
'.dockerconfigjson': b64AuthString
},
type: 'kubernetes.io/dockerconfigjson'
};
return secretJSON;
};
/**
* Creates a spec for an opaque secret using the given repository and
* account information.
*
* @param {String} secretName Name of the docker secret
* @param {String} namespace Namespace the secret should be placed in
* @param {Object} secret Object containing secret data
* @returns JSON object containing an opaque secret spec
*/
module.exports.buildOpaqueSecret = (secretName, namespace, secret) => {
const secretJSON = {
kind: 'Secret',
metadata: {
name: secretName,
namespace: namespace || defaultNamespace
},
data: secret,
type: 'Opaque'
};
return secretJSON;
};
/**
* Creates a spec for a TLS secret using the given certs.
*
* @param {String} secretName Name of the docker secret
* @param {String} namespace Namespace the secret should be placed in
* @param {String} tlsKey Private TLS key
* @param {String} tlsCert Public TLS cert
* @returns JSON object containing a TLS secret spec
*/
module.exports.buildTLSSecret = (secretName, namespace, tlsKey, tlsCert) => {
const secretJSON = {
kind: 'Secret',
metadata: {
name: secretName,
namespace: namespace || defaultNamespace
},
type: 'kubernetes.io/tls',
data: {
'tls.crt': tlsCert,
'tls.key': tlsKey
}
};
return secretJSON;
};