diff --git a/src/common/k8s-api/endpoints/resource-applier.api.ts b/src/common/k8s-api/endpoints/resource-applier.api.ts deleted file mode 100644 index 9f0c3bb6cd..0000000000 --- a/src/common/k8s-api/endpoints/resource-applier.api.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import yaml from "js-yaml"; -import type { KubeJsonApiData } from "../kube-json-api"; -import { apiBase } from "../index"; -import type { Patch } from "rfc6902"; - -export const annotations = [ - "kubectl.kubernetes.io/last-applied-configuration", -]; - -export async function update(resource: object | string): Promise { - if (typeof resource === "string") { - const parsed = yaml.load(resource); - - if (!parsed || typeof parsed !== "object") { - throw new Error("Cannot update resource to string or number"); - } - - resource = parsed; - } - - return apiBase.post("/stack", { data: resource }); -} - -export async function patch(name: string, kind: string, ns: string | undefined, patch: Patch): Promise { - return apiBase.patch("/stack", { - data: { - name, - kind, - ns, - patch, - }, - }); -} diff --git a/src/common/k8s-api/endpoints/resource-applier.api/patch.injectable.ts b/src/common/k8s-api/endpoints/resource-applier.api/patch.injectable.ts new file mode 100644 index 0000000000..c8ad3435fd --- /dev/null +++ b/src/common/k8s-api/endpoints/resource-applier.api/patch.injectable.ts @@ -0,0 +1,30 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { Patch } from "rfc6902"; +import { apiBaseInjectionToken } from "../../api-base"; +import type { KubeJsonApiData } from "../../kube-json-api"; + +export type RequestKubeObjectPatch = (name: string, kind: string, ns: string | undefined, patch: Patch) => Promise; + +const requestKubeObjectPatchInjectable = getInjectable({ + id: "request-kube-object-patch", + instantiate: (di): RequestKubeObjectPatch => { + const apiBase = di.inject(apiBaseInjectionToken); + + return (name, kind, ns, patch) => ( + apiBase.patch("/stack", { + data: { + name, + kind, + ns, + patch, + }, + }) + ); + }, +}); + +export default requestKubeObjectPatchInjectable; diff --git a/src/common/k8s-api/endpoints/resource-applier.api/update.injectable.ts b/src/common/k8s-api/endpoints/resource-applier.api/update.injectable.ts new file mode 100644 index 0000000000..52824cd86c --- /dev/null +++ b/src/common/k8s-api/endpoints/resource-applier.api/update.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { apiBaseInjectionToken } from "../../api-base"; +import type { KubeJsonApiData } from "../../kube-json-api"; + +export type RequestKubeObjectCreation = (resourceDescriptor: string) => Promise; + +const requestKubeObjectCreationInjectable = getInjectable({ + id: "request-kube-object-creation", + instantiate: (di): RequestKubeObjectCreation => { + const apiBase = di.inject(apiBaseInjectionToken); + + return (data) => apiBase.post("/stack", { data }); + }, +}); + +export default requestKubeObjectCreationInjectable; diff --git a/src/common/k8s-api/kube-object.ts b/src/common/k8s-api/kube-object.ts index 0851729bc7..02a231cf06 100644 --- a/src/common/k8s-api/kube-object.ts +++ b/src/common/k8s-api/kube-object.ts @@ -9,11 +9,15 @@ import moment from "moment"; import type { KubeJsonApiData, KubeJsonApiDataList, KubeJsonApiListMetadata } from "./kube-json-api"; import { autoBind, formatDuration, hasOptionalTypedProperty, hasTypedProperty, isObject, isString, isNumber, bindPredicate, isTypedArray, isRecord, json } from "../utils"; import type { ItemObject } from "../item.store"; -import { apiKube } from "./index"; -import * as resourceApplierApi from "./endpoints/resource-applier.api"; import type { Patch } from "rfc6902"; import assert from "assert"; import type { JsonObject } from "type-fest"; +import { asLegacyGlobalFunctionForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api"; +import requestKubeObjectPatchInjectable from "./endpoints/resource-applier.api/patch.injectable"; +import { asLegacyGlobalForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api"; +import { apiKubeInjectionToken } from "./api-kube"; +import requestKubeObjectCreationInjectable from "./endpoints/resource-applier.api/update.injectable"; +import { dump } from "js-yaml"; export type KubeJsonApiDataFor = K extends KubeObject ? KubeJsonApiData @@ -375,6 +379,12 @@ export type ScopedNamespace = ( : string | undefined ); +const resourceApplierAnnotationsForFiltering = [ + "kubectl.kubernetes.io/last-applied-configuration", +]; + +const filterOutResourceApplierAnnotations = (label: string) => !resourceApplierAnnotationsForFiltering.some(key => label.startsWith(key)); + export class KubeObject< Metadata extends KubeObjectMetadata = KubeObjectMetadata, Status = unknown, @@ -588,11 +598,11 @@ export class KubeObject< getAnnotations(filter = false): string[] { const labels = KubeObject.stringifyLabels(this.metadata.annotations); - return filter ? labels.filter(label => { - const skip = resourceApplierApi.annotations.some(key => label.startsWith(key)); + if (!filter) { + return labels; + } - return !skip; - }) : labels; + return labels.filter(filterOutResourceApplierAnnotations); } getOwnerRefs() { @@ -634,7 +644,9 @@ export class KubeObject< } } - return resourceApplierApi.patch(this.getName(), this.kind, this.getNs(), patch); + const requestKubeObjectPatch = asLegacyGlobalFunctionForExtensionApi(requestKubeObjectPatchInjectable); + + return requestKubeObjectPatch(this.getName(), this.kind, this.getNs(), patch); } /** @@ -647,11 +659,13 @@ export class KubeObject< * @deprecated use KubeApi.update instead */ async update(data: Partial): Promise { - // use unified resource-applier api for updating all k8s objects - return resourceApplierApi.update({ + const requestKubeObjectCreation = asLegacyGlobalFunctionForExtensionApi(requestKubeObjectCreationInjectable); + const descriptor = dump({ ...this.toPlainObject(), ...data, }); + + return requestKubeObjectCreation(descriptor); } /** @@ -660,6 +674,8 @@ export class KubeObject< delete(params?: object) { assert(this.selfLink, "selfLink must be present to delete self"); + const apiKube = asLegacyGlobalForExtensionApi(apiKubeInjectionToken); + return apiKube.del(this.selfLink, params); } } diff --git a/src/main/resource-applier.ts b/src/main/resource-applier.ts index 30c85e8fa0..71ce42eb62 100644 --- a/src/main/resource-applier.ts +++ b/src/main/resource-applier.ts @@ -62,11 +62,10 @@ export class ResourceApplier { } } - async apply(resource: KubernetesObject | any): Promise { - resource = this.sanitizeObject(resource); + async create(resource: string): Promise { appEventBus.emit({ name: "resource", action: "apply" }); - return this.kubectlApply(yaml.dump(resource)); + return this.kubectlApply(yaml.dump(this.sanitizeObject(resource))); } protected async kubectlApply(content: string): Promise { @@ -154,11 +153,7 @@ export class ResourceApplier { delete res.status; delete res.metadata?.resourceVersion; - const annotations = res.metadata?.annotations; - - if (annotations) { - delete annotations["kubectl.kubernetes.io/last-applied-configuration"]; - } + delete res.metadata?.annotations["kubectl.kubernetes.io/last-applied-configuration"]; return res; } diff --git a/src/main/routes/resource-applier/apply-resource-route.injectable.ts b/src/main/routes/resource-applier/apply-resource-route.injectable.ts index 45f484fd3f..49084ec8b7 100644 --- a/src/main/routes/resource-applier/apply-resource-route.injectable.ts +++ b/src/main/routes/resource-applier/apply-resource-route.injectable.ts @@ -5,17 +5,19 @@ import { getRouteInjectable } from "../../router/router.injectable"; import { apiPrefix } from "../../../common/vars"; import { ResourceApplier } from "../../resource-applier"; -import { clusterRoute } from "../../router/route"; +import { payloadValidatedClusterRoute } from "../../router/route"; +import Joi from "joi"; -const applyResourceRouteInjectable = getRouteInjectable({ +const createResourceRouteInjectable = getRouteInjectable({ id: "apply-resource-route", - instantiate: () => clusterRoute({ + instantiate: () => payloadValidatedClusterRoute({ method: "post", path: `${apiPrefix}/stack`, + payloadValidator: Joi.string(), })(async ({ cluster, payload }) => ({ - response: await new ResourceApplier(cluster).apply(payload), + response: await new ResourceApplier(cluster).create(payload), })), }); -export default applyResourceRouteInjectable; +export default createResourceRouteInjectable; diff --git a/src/renderer/components/dock/create-resource/view.tsx b/src/renderer/components/dock/create-resource/view.tsx index a9245e4494..525ca325ea 100644 --- a/src/renderer/components/dock/create-resource/view.tsx +++ b/src/renderer/components/dock/create-resource/view.tsx @@ -6,14 +6,13 @@ import React from "react"; import type { SelectOption } from "../../select"; import { Select } from "../../select"; -import yaml from "js-yaml"; +import yaml, { dump } from "js-yaml"; import type { IComputedValue } from "mobx"; import { makeObservable, observable } from "mobx"; import { observer } from "mobx-react"; import type { CreateResourceTabStore } from "./store"; import { EditorPanel } from "../editor-panel"; import { InfoPanel } from "../info-panel"; -import * as resourceApplierApi from "../../../../common/k8s-api/endpoints/resource-applier.api"; import { Notifications } from "../../notifications"; import logger from "../../../../common/logger"; import type { ApiManager } from "../../../../common/k8s-api/api-manager"; @@ -28,6 +27,8 @@ import type { GetDetailsUrl } from "../../kube-detail-params/get-details-url.inj import apiManagerInjectable from "../../../../common/k8s-api/api-manager/manager.injectable"; import getDetailsUrlInjectable from "../../kube-detail-params/get-details-url.injectable"; import navigateInjectable from "../../../navigation/navigate.injectable"; +import type { RequestKubeObjectCreation } from "../../../../common/k8s-api/endpoints/resource-applier.api/update.injectable"; +import requestKubeObjectCreationInjectable from "../../../../common/k8s-api/endpoints/resource-applier.api/update.injectable"; export interface CreateResourceProps { tabId: string; @@ -39,6 +40,7 @@ interface Dependencies { apiManager: ApiManager; navigate: Navigate; getDetailsUrl: GetDetailsUrl; + requestKubeObjectCreation: RequestKubeObjectCreation; } @observer @@ -68,7 +70,7 @@ class NonInjectedCreateResource extends React.Component => { - const { apiManager, getDetailsUrl, navigate } = this.props; + const { apiManager, getDetailsUrl, navigate, requestKubeObjectCreation } = this.props; if (this.error || !this.data?.trim()) { // do not save when field is empty or there is an error @@ -84,7 +86,7 @@ class NonInjectedCreateResource extends React.Component { try { - const data = await resourceApplierApi.update(resource); + const data = await requestKubeObjectCreation(dump(resource)); const { kind, apiVersion, metadata: { name, namespace }} = data; const showDetails = () => { @@ -168,5 +170,6 @@ export const CreateResource = withInjectables apiManager: di.inject(apiManagerInjectable), getDetailsUrl: di.inject(getDetailsUrlInjectable), navigate: di.inject(navigateInjectable), + requestKubeObjectCreation: di.inject(requestKubeObjectCreationInjectable), }), });