1 | "use strict";
|
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
|
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
|
4 | };
|
5 | Object.defineProperty(exports, "__esModule", { value: true });
|
6 | const request_1 = __importDefault(require("request"));
|
7 | const label_1 = require("./label");
|
8 | const meta_1 = require("./types/meta");
|
9 | const debug = require("debug")("kubernetes:client");
|
10 | exports.patchKindStrategicMergePatch = "application/stategic-merge-patch+json";
|
11 | exports.patchKindMergePatch = "application/merge-patch+json";
|
12 | exports.patchKindJSONPatch = "application/json-patch+json";
|
13 | const joinURL = (left, right) => (left + "/" + right).replace(/([^:])(\/\/)/g, "$1/");
|
14 | class KubernetesRESTClient {
|
15 | constructor(config) {
|
16 | this.config = config;
|
17 | }
|
18 | request(url, body, method = "POST", additionalOptions = {}) {
|
19 | const absoluteURL = joinURL(this.config.apiServerURL, url);
|
20 | return new Promise((res, rej) => {
|
21 | let opts = {
|
22 | method,
|
23 | url: absoluteURL,
|
24 | json: true,
|
25 | };
|
26 | if (body) {
|
27 | opts.json = body;
|
28 | }
|
29 | opts = this.config.mapRequestOptions(opts);
|
30 | if (additionalOptions.headers) {
|
31 | additionalOptions.headers = Object.assign(Object.assign({}, (opts.headers || {})), additionalOptions.headers);
|
32 | }
|
33 | opts = Object.assign(Object.assign({}, opts), additionalOptions);
|
34 | debug(`executing ${method} request on ${opts.url}`);
|
35 | request_1.default(opts, (err, response, responseBody) => {
|
36 | if (err) {
|
37 | rej(err);
|
38 | return;
|
39 | }
|
40 | if (meta_1.isStatus(responseBody) && responseBody.status === "Failure") {
|
41 | rej(new Error(responseBody.message));
|
42 | return;
|
43 | }
|
44 | debug(`${method} request on ${opts.url} succeeded with status ${response.statusCode}: ${responseBody}`);
|
45 | res(responseBody);
|
46 | });
|
47 | });
|
48 | }
|
49 | post(url, body) {
|
50 | return this.request(url, body, "POST");
|
51 | }
|
52 | put(url, body) {
|
53 | return this.request(url, body, "PUT");
|
54 | }
|
55 | patch(url, body, patchKind) {
|
56 | return this.request(url, body, "PATCH", { headers: {
|
57 | "Content-Type": patchKind,
|
58 | } });
|
59 | }
|
60 | delete(url, deleteOptions, queryParams = {}, body) {
|
61 | const opts = {};
|
62 | opts.qs = queryParams;
|
63 | if (deleteOptions && deleteOptions.labelSelector) {
|
64 | opts.qs.labelSelector = label_1.selectorToQueryString(deleteOptions.labelSelector);
|
65 | }
|
66 | if (deleteOptions && deleteOptions.fieldSelector) {
|
67 | opts.qs.fieldSelector = label_1.selectorToQueryString(deleteOptions.fieldSelector);
|
68 | }
|
69 | return this.request(url, body, "DELETE", opts);
|
70 | }
|
71 | watch(url, onUpdate, onError, watchOpts = {}) {
|
72 | const absoluteURL = joinURL(this.config.apiServerURL, url);
|
73 | let opts = {
|
74 | url: absoluteURL,
|
75 | qs: { watch: "true" },
|
76 | };
|
77 | if (watchOpts.labelSelector) {
|
78 | opts.qs.labelSelector = label_1.selectorToQueryString(watchOpts.labelSelector);
|
79 | }
|
80 | if (watchOpts.fieldSelector) {
|
81 | opts.qs.fieldSelector = label_1.selectorToQueryString(watchOpts.fieldSelector);
|
82 | }
|
83 | if (watchOpts.resourceVersion) {
|
84 | opts.qs.resourceVersion = watchOpts.resourceVersion;
|
85 | }
|
86 | opts = this.config.mapRequestOptions(opts);
|
87 | let lastVersion = watchOpts.resourceVersion || 0;
|
88 | debug(`executing WATCH request on ${absoluteURL} (starting revision ${lastVersion})`);
|
89 | return new Promise((res, rej) => {
|
90 | const req = request_1.default(opts, (err, response, bodyString) => {
|
91 | if (err) {
|
92 | rej(err);
|
93 | return;
|
94 | }
|
95 | debug(`%o request on %o completed with status %o: %O`, "WATCH", opts.url, response.statusCode, bodyString);
|
96 | if (response.statusCode && response.statusCode >= 400) {
|
97 | if (response.statusCode === 410) {
|
98 | debug(`last known resource has expired -- resync required`);
|
99 | res({ resourceVersion: lastVersion, resyncRequired: true });
|
100 | return;
|
101 | }
|
102 | rej(new Error("Unexpected status code: " + response.statusCode));
|
103 | return;
|
104 | }
|
105 | if (bodyString.length === 0) {
|
106 | debug(`WATCH request on ${url} returned empty response`);
|
107 | res({ resourceVersion: lastVersion });
|
108 | return;
|
109 | }
|
110 | let body;
|
111 | try {
|
112 | body = JSON.parse(bodyString);
|
113 | }
|
114 | catch (err) {
|
115 | const bodyLines = bodyString.split("\n");
|
116 | for (const line of bodyLines) {
|
117 | if (line === "") {
|
118 | continue;
|
119 | }
|
120 | try {
|
121 | const parsedLine = JSON.parse(line);
|
122 | if (parsedLine.type === "ADDED" || parsedLine.type === "MODIFIED" || parsedLine.type === "DELETED") {
|
123 | const resourceVersion = parseInt(parsedLine.object.metadata.resourceVersion || "0", 10);
|
124 | if (resourceVersion > lastVersion) {
|
125 | debug(`watch: emitting missed ${parsedLine.type} event for ${parsedLine.object.metadata.name}`);
|
126 | lastVersion = resourceVersion;
|
127 | onUpdate(parsedLine);
|
128 | }
|
129 | }
|
130 | }
|
131 | catch (err) {
|
132 | debug(`watch: could not parse JSON line '${line}'`);
|
133 | rej(err);
|
134 | return;
|
135 | }
|
136 | }
|
137 | res({ resourceVersion: lastVersion });
|
138 | return;
|
139 | }
|
140 | if (meta_1.isStatus(body) && body.status === "Failure") {
|
141 | debug(`watch: failed with status %O`, body);
|
142 | rej(body.message);
|
143 | return;
|
144 | }
|
145 | res({ resourceVersion: lastVersion });
|
146 | });
|
147 | let buffer = "";
|
148 | req.on("data", chunk => {
|
149 | if (chunk instanceof Buffer) {
|
150 | chunk = chunk.toString("utf-8");
|
151 | }
|
152 | debug("WATCH request on %o received %d bytes of data", opts.url, chunk.length);
|
153 | buffer += chunk;
|
154 | try {
|
155 | const obj = JSON.parse(buffer);
|
156 | buffer = "";
|
157 | const resourceVersion = obj.object.metadata.resourceVersion ? parseInt(obj.object.metadata.resourceVersion, 10) : -1;
|
158 | if (resourceVersion > lastVersion) {
|
159 | debug(`watch: emitting ${obj.type} event for ${obj.object.metadata.name}`);
|
160 | lastVersion = resourceVersion;
|
161 | onUpdate(obj);
|
162 | }
|
163 | }
|
164 | catch (err) {
|
165 | onError(err);
|
166 | }
|
167 | });
|
168 | });
|
169 | }
|
170 | get(url, listOptions = {}) {
|
171 | const absoluteURL = joinURL(this.config.apiServerURL, url);
|
172 | const { labelSelector, fieldSelector } = listOptions;
|
173 | return new Promise((res, rej) => {
|
174 | let opts = {
|
175 | url: absoluteURL,
|
176 | qs: {},
|
177 | };
|
178 | if (labelSelector) {
|
179 | opts.qs.labelSelector = label_1.selectorToQueryString(labelSelector);
|
180 | }
|
181 | if (fieldSelector) {
|
182 | opts.qs.fieldSelector = label_1.selectorToQueryString(fieldSelector);
|
183 | }
|
184 | opts = this.config.mapRequestOptions(opts);
|
185 | debug(`executing GET request on ${opts.url}`);
|
186 | request_1.default(opts, (err, response, body) => {
|
187 | if (err) {
|
188 | rej(err);
|
189 | return;
|
190 | }
|
191 | if (response.statusCode === 404) {
|
192 | debug(`GET request on %o failed with status %o`, opts.url, response.statusCode);
|
193 | res(undefined);
|
194 | return;
|
195 | }
|
196 | try {
|
197 | body = JSON.parse(body);
|
198 | }
|
199 | catch (err) {
|
200 | rej(err);
|
201 | return;
|
202 | }
|
203 | if (meta_1.isStatus(body) && body.status === "Failure") {
|
204 | if (body.code === 404) {
|
205 | res(undefined);
|
206 | return;
|
207 | }
|
208 | debug(`executing GET request on %o failed. response body: %O`, response.statusCode, body);
|
209 | rej(new Error(body.message));
|
210 | return;
|
211 | }
|
212 | debug(`GET request on %o succeeded with status %o: %O`, opts.url, response.statusCode, body);
|
213 | res(body);
|
214 | });
|
215 | });
|
216 | }
|
217 | }
|
218 | exports.KubernetesRESTClient = KubernetesRESTClient;
|
219 |
|
\ | No newline at end of file |