-
Notifications
You must be signed in to change notification settings - Fork 0
/
apimap.js
193 lines (162 loc) · 6.5 KB
/
apimap.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
// Third-party libs
const https = require('https');
// Local libs
const Request = require('./request');
const Utils = require('./utils');
const Client = require('./client');
// Holds API maps for each version of a kubernetes cluster encountered
const apiMaps = {};
/**
* Takes the cluster version, an API resources response from the cluster, and the
* preferred version of the API endpoint whose resources are being mapped.
*
* @param {String} clusterVer Version of the Kubernetes cluster, e.g. 1.7 or 1.8
* @param {Object} resourcesResp Response from the cluster of a /apis/[group]/[version] request
* @param {String} preferredGroupVer Preferred version of the group, e.g. apps/v1beta1
*/
function mapGroupResources (clusterVer, resourcesResp, preferredGroupVer) {
const groupVer = resourcesResp.groupVersion;
const extractVerRegex = /^([^/]+\/)?(v[0-9]+([a-z]+[0-9]+)?)$/;
const ver = extractVerRegex.exec(groupVer)[2];
const resources = resourcesResp.resources;
if (!apiMaps[clusterVer]) apiMaps[clusterVer] = {};
for (const resource of resources) {
const name = resource.name;
// Only process the original resource, no sub-routes
if (name.indexOf('/') !== -1) continue;
// Get the current mapped group version
let currGroupVer, currVer;
if (apiMaps[clusterVer][name]) {
currGroupVer = apiMaps[clusterVer][name].version;
currVer = extractVerRegex.exec(currGroupVer)[2];
}
// If the current mapped matches the preferred version, just keep that
if (currGroupVer && preferredGroupVer && currGroupVer === preferredGroupVer) continue;
// If there isn't any current mapped group version or the new group version is greater
// than the current one, set the current group version
else if (!currGroupVer || (currGroupVer && compareAPIVersions(currVer, ver) < 0)) {
apiMaps[clusterVer][name] = {
version: groupVer,
namespaced: resource.namespaced
};
}
}
return apiMaps[clusterVer];
}
/**
* Classic compare function but takes Kubernetes API version, e.g. v1beta1 and v1alpha2.
*
* @param {String} ver1 Kubernetes version to compare to
* @param {String} ver2 Kubernetes version to compare with
* @returns < 0 if ver1 is a lower version than ver2, === 0 if ver1 is equal to ver2,
* and > 0 if ver 1 is a greater version than ver2
*/
function compareAPIVersions (ver1, ver2) {
if (ver1 && !ver2) return 1;
else if (!ver1 && ver2) return -1;
else if (!ver1 && !ver2) return 0;
const verRegex = /([a-z]+)([0-9]+)([a-z]+)?([0-9]+)?/;
const ver1Extraction = verRegex.exec(ver1);
const ver2Extraction = verRegex.exec(ver2);
const ver1Major = ver1Extraction[2];
const ver1MinorStr = ver1Extraction[3];
const ver1Minor = ver1Extraction[4];
const ver2Major = ver2Extraction[2];
const ver2MinorStr = ver2Extraction[3];
const ver2Minor = ver2Extraction[4];
if (ver1Major > ver2Major) return 1;
else if (ver1Major < ver2Major) return -1;
if (ver1MinorStr === 'beta' && ver2MinorStr === 'alpha') return 1;
else if (ver1MinorStr === 'alpha' && ver2MinorStr === 'beta') return -1;
else if (ver1MinorStr && !ver2MinorStr) return -1;
else if (!ver1MinorStr && ver2MinorStr) return 1;
if (ver1Minor > ver2Minor) return 1;
else if (ver1Minor < ver2Minor) return -1;
return 0;
}
/**
* Given a cluster version (e.g. '1.7', '1.8') and a resource type (e.g. 'pod', 'deployment'),
* returns the API group to use for that resource, or all the resources if only the version
* is provided.
*
* @param {String} clusterVersion Version of the cluster to determine an API group of
* @param {String} resourceType (Optional )Type of the resource whose API group is being determined
* @returns {Object} Contains info about the API group, such as its version string and whether
* or not it's namespaced; returns all the resource info objects in the
* cluster if only the cluster version is provided.
*/
module.exports.getGroupInfo = function (clusterVersion, resourceType) {
if (!clusterVersion || typeof(clusterVersion) !== 'string') return undefined;
if (!resourceType || typeof(resourceType) !== 'string') return undefined;
resourceType = resourceType.toLowerCase();
if (apiMaps && typeof(apiMaps) === 'object') {
if (apiMaps[clusterVersion] && typeof(apiMaps[clusterVersion]) === 'object') {
let apiGroupInfo = apiMaps[clusterVersion][resourceType];
// Try making the string plural
if (!apiGroupInfo) {
apiGroupInfo = apiMaps[clusterVersion][resourceType + 's'];
}
// Give up and send the whole thing
if (!apiGroupInfo) {
apiGroupInfo = apiMaps[clusterVersion];
}
return apiGroupInfo;
}
}
return undefined;
};
/**
* Maps cluster resources to cluster API endpoints.
*
* @param {Object} kubeconfig Kubeconfig representing the cluster
* @returns Promise that ALWAYS resolves with two parameters: (errs, map). The reason
* the promise always resolves is that way we can attempt to map as may APIs
* as possible while still handling and returning errors caused by trying to
* map some other part of the API.
*/
module.exports.buildAPIMap = async (kubeconfig) => {
if (!kubeconfig || typeof(kubeconfig) !== 'object') {
const err = new Error('Invalid \'kubeconfig\' object given');
err.status = 400;
return Promise.reject(err);
}
const groupReqs = [];
const errs = [];
const versionObj = await Client.getVersion(kubeconfig);
const clusterVer = Utils.prettifyVersion(versionObj.gitVersion, 2);
// Add the 'core' API group
const coreGroupReq = Request.cluster(kubeconfig, 'get', '/api/v1').then((res) => {
Request.parseStatus(res, new Error());
return mapGroupResources(clusterVer, res.body);
}).catch((err) => {
errs.push(err);
});
groupReqs.push(coreGroupReq);
// Add all other API groups
const apiGroups = await Request.cluster(kubeconfig, 'get', '/apis').then((res) => {
Request.parseStatus(res, new Error());
return res.body.groups;
}).catch((err) => {
errs.push(err);
return undefined;
});
if (apiGroups) {
for (const group of apiGroups) {
const preferredGroupVer = group.preferredVersion.groupVersion;
for (const version of group.versions) {
const groupReq = Request.cluster(kubeconfig, 'get', `/apis/${version.groupVersion}`).then((res) => {
Request.parseStatus(res, new Error());
return mapGroupResources(clusterVer, res.body, preferredGroupVer);
}).catch((err) => {
errs.push(err);
return undefined;
});
groupReqs.push(groupReq);
}
}
}
return Promise.all(groupReqs).then(() => {
if (errs.length > 0) throw errs;
else return apiMaps[clusterVer];
});
};