@atomist/sdm
Version:
Atomist Software Delivery Machine SDK
234 lines • 9.41 kB
JavaScript
;
/*
* Copyright © 2020 Atomist, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.namespaceRequired = exports.appendName = exports.K8sObjectApi = exports.K8sPatchStrategies = void 0;
const k8s = require("@kubernetes/client-node");
const request = require("request");
const error_1 = require("../support/error");
const namespace_1 = require("../support/namespace");
const resource_1 = require("./resource");
/**
* Valid Content-Type header values for patch operations. See
* https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/
* for details.
*/
var K8sPatchStrategies;
(function (K8sPatchStrategies) {
/** Diff-like JSON format. */
K8sPatchStrategies["JsonPatch"] = "application/json-patch+json";
/** Simple merge. */
K8sPatchStrategies["MergePatch"] = "application/merge-patch+json";
/** Merge with different strategies depending on field metadata. */
K8sPatchStrategies["StrategicMergePatch"] = "application/strategic-merge-patch+json";
})(K8sPatchStrategies = exports.K8sPatchStrategies || (exports.K8sPatchStrategies = {}));
/**
* Dynamically construct Kubernetes API request URIs so client does
* not have to know what type of object it is acting on, create the
* appropriate client, and call the appropriate method.
*/
class K8sObjectApi extends k8s.ApisApi {
/**
* Read any Kubernetes resource.
*/
async create(spec, options) {
const requestOptions = this.baseRequestOptions("POST", options);
requestOptions.uri += await this.specUriPath(spec, "create");
requestOptions.body = spec;
return this.requestPromise(requestOptions);
}
/**
* Delete any Kubernetes resource.
*/
async delete(spec, body, options) {
const requestOptions = this.baseRequestOptions("DELETE", options);
requestOptions.uri += await this.specUriPath(spec, "delete");
requestOptions.body = body || K8sObjectApi.defaultDeleteBody;
return this.requestPromise(requestOptions);
}
/**
* List any Kubernetes resource.
*/
async list(spec, options) {
const requestOptions = this.baseRequestOptions("GET", options);
requestOptions.uri += await this.specUriPath(spec, "list");
return this.requestPromise(requestOptions);
}
/**
* Patch any Kubernetes resource.
*/
async patch(spec, options) {
const requestOptions = this.baseRequestOptions("PATCH", options);
requestOptions.uri += await this.specUriPath(spec, "patch");
requestOptions.body = spec;
return this.requestPromise(requestOptions);
}
/**
* Read any Kubernetes resource.
*/
async read(spec, options) {
const requestOptions = this.baseRequestOptions("GET", options);
requestOptions.uri += await this.specUriPath(spec, "read");
return this.requestPromise(requestOptions);
}
/**
* Replace any Kubernetes resource.
*/
async replace(spec, options) {
const requestOptions = this.baseRequestOptions("PUT", options);
requestOptions.uri += await this.specUriPath(spec, "replace");
requestOptions.body = spec;
return this.requestPromise(requestOptions);
}
/**
* Get metadata from Kubernetes API for resources described by
* `kind` and `apiVersion`. If it is unable to find the resource
* `kind` under the provided `apiVersion`, `undefined` is
* returned.
*/
async resource(apiVersion, kind) {
try {
const requestOptions = this.baseRequestOptions();
const prefix = (apiVersion.includes("/")) ? "apis" : "api";
requestOptions.uri += [prefix, apiVersion].join("/");
const getApiResponse = await this.requestPromise(requestOptions);
const apiResourceList = getApiResponse.body;
return apiResourceList.resources.find(r => r.kind === kind);
}
catch (e) {
e.message = `Failed to fetch resource metadata for ${apiVersion}/${kind}: ${e.message}`;
throw e;
}
}
/**
* Generate request options. Largely copied from the common
* elements of @kubernetes/client-node action methods.
*/
baseRequestOptions(method = "GET", options) {
const localVarPath = this.basePath + "/";
const queryParameters = {};
const headerParams = Object.assign({}, this.defaultHeaders, K8sObjectApi.methodHeaders(method), (options === null || options === void 0 ? void 0 : options.headers) || {});
const requestOptions = {
method,
qs: queryParameters,
headers: headerParams,
uri: localVarPath,
useQuerystring: this._useQuerystring,
json: true,
};
this.authentications.BearerToken.applyToRequest(requestOptions);
this.authentications.default.applyToRequest(requestOptions);
return requestOptions;
}
/**
* Use spec information to construct resource URI path. If any
* required information in not provided, an Error is thrown. If an
* `apiVersion` is not provided, "v1" is used. If a `metadata.namespace`
* is not provided for a request that requires one, "default" is used.
*
* @param spec resource spec which must kind and apiVersion properties
* @param action API action, see [[K8sApiAction]]
* @return tail of resource-specific URI
*/
async specUriPath(spec, action) {
if (!spec.kind) {
throw new Error(`Spec does not contain kind: ${resource_1.logObject(spec)}`);
}
if (!spec.apiVersion) {
spec.apiVersion = "v1";
}
if (!spec.metadata) {
spec.metadata = {};
}
const resource = await this.resource(spec.apiVersion, spec.kind);
if (!resource) {
throw new Error(`Unrecognized API version and kind: ${spec.apiVersion} ${spec.kind}`);
}
if (namespaceRequired(resource, action) && !spec.metadata.namespace) {
spec.metadata.namespace = namespace_1.K8sDefaultNamespace;
}
const prefix = (spec.apiVersion.includes("/")) ? "apis" : "api";
const parts = [prefix, spec.apiVersion];
if (resource.namespaced && spec.metadata.namespace) {
parts.push("namespaces", spec.metadata.namespace);
}
parts.push(resource.name);
if (appendName(action)) {
if (!spec.metadata.name) {
throw new Error(`Spec does not contain name: ${resource_1.logObject(spec)}`);
}
parts.push(spec.metadata.name);
}
return parts.join("/").toLowerCase();
}
/**
* Wrap request in a Promise. Largely copied from @kubernetes/client-node/dist/api.js.
*/
requestPromise(requestOptions) {
return new Promise((resolve, reject) => {
request(requestOptions, (error, response, body) => {
if (error) {
reject(error);
}
else {
if (response.statusCode >= 200 && response.statusCode <= 299) {
resolve({ response, body });
}
else {
reject(error_1.requestError({ response, body }));
}
}
});
});
}
/**
* Return default headers based on action.
*/
static methodHeaders(method) {
return (method === "PATCH") ? { "Content-Type": K8sPatchStrategies.StrategicMergePatch } : {};
}
}
exports.K8sObjectApi = K8sObjectApi;
K8sObjectApi.defaultDeleteBody = { propagationPolicy: "Background" };
/**
* Return whether the name of the resource should be appended to the
* API URI path. When creating and listing resources, it is not
* appended.
*
* @param action API action, see [[K8sApiAction]]
* @return true if name should be appended to URI
*/
function appendName(action) {
return !(action === "create" || action === "list");
}
exports.appendName = appendName;
/**
* Return whether namespace must be included in resource API URI.
* It returns true of the resource is namespaced and the action is
* not "list". The namespace can be provided when the action is
* "list", but it need not be.
*
* @param resource resource metadata
* @param action API action, see [[K8sApiAction]]
* @return true is the namespace is required in the API URI path
*/
function namespaceRequired(resource, action) {
// return action !== "list" || resource.namespaced;
// return !(action === "list" || !resource.namespaced);
return resource.namespaced && action !== "list";
}
exports.namespaceRequired = namespaceRequired;
//# sourceMappingURL=api.js.map