From 928ee8deead0820c9a05e3d743042c35b6c9b809 Mon Sep 17 00:00:00 2001 From: David Dooling Date: Wed, 17 Jun 2020 19:48:22 -0500 Subject: [PATCH] Remove k8vent annotation, update k8s Remove the deprecated k8vent annotation. Update k8s to support Kubernetes 1.16 and newer, replacing extensions/v1beta1 with networking.k8s.io/v1beta1. Prettier. [changelog:removed] --- lib/core/pack/k8s/container.ts | 257 +++++++++--------- lib/core/pack/k8s/kubernetes/application.ts | 35 +-- lib/core/pack/k8s/kubernetes/clients.ts | 24 +- lib/core/pack/k8s/kubernetes/delete.ts | 47 ++-- lib/core/pack/k8s/kubernetes/deployment.ts | 8 - lib/core/pack/k8s/kubernetes/fetch.ts | 52 ++-- lib/core/pack/k8s/kubernetes/ingress.ts | 38 ++- lib/core/pack/k8s/kubernetes/resource.ts | 2 +- test/core/pack/k8s/kubernetes/api.test.ts | 225 +++++++++++---- test/core/pack/k8s/kubernetes/clients.test.ts | 6 +- .../pack/k8s/kubernetes/deployment.test.ts | 17 -- test/core/pack/k8s/kubernetes/fetch.test.ts | 175 +++++++----- test/core/pack/k8s/kubernetes/ingress.test.ts | 23 +- .../core/pack/k8s/kubernetes/resource.test.ts | 18 +- test/core/pack/k8s/kubernetes/spec.test.ts | 82 +++--- test/core/pack/k8s/sync/application.test.ts | 70 +++-- 16 files changed, 596 insertions(+), 483 deletions(-) diff --git a/lib/core/pack/k8s/container.ts b/lib/core/pack/k8s/container.ts index 0c7da6c84..0331ccd0d 100644 --- a/lib/core/pack/k8s/container.ts +++ b/lib/core/pack/k8s/container.ts @@ -26,15 +26,9 @@ import * as os from "os"; import * as path from "path"; import * as request from "request"; import { Writable } from "stream"; -import { - DeepPartial, - Merge, -} from "ts-essentials"; +import { DeepPartial, Merge } from "ts-essentials"; import { minimalClone } from "../../../api-helper/goal/minimalClone"; -import { - goalData, - sdmGoalTimeout, -} from "../../../api-helper/goal/sdmGoal"; +import { goalData, sdmGoalTimeout } from "../../../api-helper/goal/sdmGoal"; import { RepoContext } from "../../../api/context/SdmContext"; import { ExecuteGoalResult } from "../../../api/goal/ExecuteGoalResult"; import { @@ -42,21 +36,13 @@ import { GoalProjectListenerEvent, GoalProjectListenerRegistration, } from "../../../api/goal/GoalInvocation"; -import { - GoalWithFulfillment, - ImplementationRegistration, -} from "../../../api/goal/GoalWithFulfillment"; +import { GoalWithFulfillment, ImplementationRegistration } from "../../../api/goal/GoalWithFulfillment"; import { SdmGoalEvent } from "../../../api/goal/SdmGoalEvent"; import { GoalScheduler } from "../../../api/goal/support/GoalScheduler"; import { ServiceRegistrationGoalDataKey } from "../../../api/registration/ServiceRegistration"; import { ProgressLog } from "../../../spi/log/ProgressLog"; import { SdmGoalState } from "../../../typings/types"; -import { - CacheEntry, - CacheOutputGoalDataKey, - cachePut, - cacheRestore, -} from "../../goal/cache/goalCaching"; +import { CacheEntry, CacheOutputGoalDataKey, cachePut, cacheRestore } from "../../goal/cache/goalCaching"; import { Container, ContainerInput, @@ -69,30 +55,18 @@ import { GoalContainerVolume, } from "../../goal/container/container"; import { prepareSecrets } from "../../goal/container/provider"; -import { - containerEnvVars, - prepareInputAndOutput, - processResult, -} from "../../goal/container/util"; +import { containerEnvVars, prepareInputAndOutput, processResult } from "../../goal/container/util"; import { toArray } from "../../util/misc/array"; import { loadKubeConfig } from "./kubernetes/config"; -import { - k8sJobEnv, - KubernetesGoalScheduler, - readNamespace, -} from "./scheduler/KubernetesGoalScheduler"; -import { - K8sServiceRegistrationType, - K8sServiceSpec, -} from "./scheduler/service"; +import { k8sJobEnv, KubernetesGoalScheduler, readNamespace } from "./scheduler/KubernetesGoalScheduler"; +import { K8sServiceRegistrationType, K8sServiceSpec } from "./scheduler/service"; import { k8sErrMsg } from "./support/error"; // tslint:disable:max-file-line-count /** Merge of base and Kubernetes goal container interfaces. */ -export type K8sGoalContainer = - Merge> - & Pick; +export type K8sGoalContainer = Merge> & + Pick; /** Merge of base and Kubernetes goal container volume interfaces. */ export type K8sGoalContainerVolume = Merge; @@ -103,9 +77,13 @@ export type K8sGoalContainerSpec = Omit; * Function signature for callback that can modify and return the * [[ContainerRegistration]] object. */ -export type K8sContainerSpecCallback = - (r: K8sContainerRegistration, p: GitProject, g: Container, e: SdmGoalEvent, ctx: RepoContext) => - Promise>; +export type K8sContainerSpecCallback = ( + r: K8sContainerRegistration, + p: GitProject, + g: Container, + e: SdmGoalEvent, + ctx: RepoContext, +) => Promise>; /** * Additional options for Kubernetes implementation of container goals. @@ -152,7 +130,7 @@ export interface K8sContainerRegistration extends ContainerRegistration { export const k8sContainerScheduler: ContainerScheduler = (goal, registration: K8sContainerRegistration) => { goal.addFulfillment({ goalExecutor: executeK8sJob(), - ...registration as ImplementationRegistration, + ...(registration as ImplementationRegistration), }); goal.addFulfillmentCallback({ @@ -167,7 +145,7 @@ export const k8sContainerScheduler: ContainerScheduler = (goal, registration: K8 export const k8sSkillContainerScheduler: ContainerScheduler = (goal, registration: K8sContainerRegistration) => { goal.addFulfillment({ goalExecutor: executeK8sJob(), - ...registration as ImplementationRegistration, + ...(registration as ImplementationRegistration), }); }; @@ -183,16 +161,20 @@ export function k8sFulfillmentCallback( return async (goalEvent, repoContext) => { let spec: K8sContainerRegistration = _.cloneDeep(registration); if (registration.callback) { - spec = await repoContext.configuration.sdm.projectLoader.doWithProject({ - ...repoContext, - readOnly: true, - cloneOptions: minimalClone(goalEvent.push, { detachHead: true }), - }, async p => { - return { - ...spec, - ...(await registration.callback(_.cloneDeep(registration), p, goal, goalEvent, repoContext)) || {}, - }; - }); + spec = await repoContext.configuration.sdm.projectLoader.doWithProject( + { + ...repoContext, + readOnly: true, + cloneOptions: minimalClone(goalEvent.push, { detachHead: true }), + }, + async p => { + return { + ...spec, + ...((await registration.callback(_.cloneDeep(registration), p, goal, goalEvent, repoContext)) || + {}), + }; + }, + ); } if (!spec.containers || spec.containers.length < 1) { @@ -213,7 +195,9 @@ export function k8sFulfillmentCallback( } const goalSchedulers: GoalScheduler[] = toArray(repoContext.configuration.sdm.goalScheduler) || []; - const k8sScheduler = goalSchedulers.find(gs => gs instanceof KubernetesGoalScheduler) as KubernetesGoalScheduler; + const k8sScheduler = goalSchedulers.find( + gs => gs instanceof KubernetesGoalScheduler, + ) as KubernetesGoalScheduler; if (!k8sScheduler) { throw new Error("Failed to find KubernetesGoalScheduler in goal schedulers"); } @@ -283,14 +267,12 @@ export function k8sFulfillmentCallback( const parameters = JSON.parse((goalEvent as any).parameters || "{}"); const secrets = await prepareSecrets( - _.merge({}, registration.containers[0], (parameters["@atomist/sdm/secrets"] || {})), repoContext); + _.merge({}, registration.containers[0], parameters["@atomist/sdm/secrets"] || {}), + repoContext, + ); delete spec.containers[0].secrets; [...spec.containers, ...spec.initContainers].forEach(c => { - c.env = [ - ...(secrets.env || []), - ...containerEnvs, - ...(c.env || []), - ]; + c.env = [...(secrets.env || []), ...containerEnvs, ...(c.env || [])]; }); if (!!secrets?.files) { for (const file of secrets.files) { @@ -298,8 +280,7 @@ export function k8sFulfillmentCallback( const dirname = path.dirname(file.mountPath); let secretName = `secret-${guid().split("-")[0]}`; - const vm = (copyContainer.volumeMounts || []) - .find(m => m.mountPath === dirname); + const vm = (copyContainer.volumeMounts || []).find(m => m.mountPath === dirname); if (!!vm) { secretName = vm.name; } else { @@ -330,20 +311,14 @@ export function k8sFulfillmentCallback( }); } } - spec.initContainers = [ - copyContainer, - ...spec.initContainers, - ]; + spec.initContainers = [copyContainer, ...spec.initContainers]; - const serviceSpec: { type: string, spec: K8sServiceSpec } = { + const serviceSpec: { type: string; spec: K8sServiceSpec } = { type: K8sServiceRegistrationType.K8sService, spec: { container: spec.containers, initContainer: spec.initContainers, - volume: [ - ...ioVolumes, - ...(spec.volumes || []), - ], + volume: [...ioVolumes, ...(spec.volumes || [])], volumeMount: ioVolumeMounts, }, }; @@ -432,36 +407,42 @@ export function executeK8sJob(): ExecuteGoal { throw new Error("Failed to read k8s ContainerRegistration from goal data"); } if (!data[ContainerRegistrationGoalDataKey]) { - throw new Error(`Goal ${gi.goal.uniqueName} has no Kubernetes container registration: ${gi.goalEvent.data}`); + throw new Error( + `Goal ${gi.goal.uniqueName} has no Kubernetes container registration: ${gi.goalEvent.data}`, + ); } const registration: K8sContainerRegistration = data[ContainerRegistrationGoalDataKey]; if (process.env.ATOMIST_ISOLATED_GOAL_INIT === "true") { - return configuration.sdm.projectLoader.doWithProject({ - ...gi, - readOnly: false, - cloneDir: projectDir, - cloneOptions: minimalClone(goalEvent.push, { detachHead: true }), - }, async project => { - try { - await prepareInputAndOutput(inputDir, outputDir, gi); - } catch (e) { - const message = `Failed to prepare input and output for goal ${goalEvent.name}: ${e.message}`; - progressLog.write(message); - return { code: 1, message }; - } - const secrets = await prepareSecrets( - _.merge({}, registration.containers[0], ((gi.parameters || {})["@atomist/sdm/secrets"] || {})), gi); - if (!!secrets?.files) { - for (const file of secrets.files) { - await fs.writeFile(file.mountPath, file.value); + return configuration.sdm.projectLoader.doWithProject( + { + ...gi, + readOnly: false, + cloneDir: projectDir, + cloneOptions: minimalClone(goalEvent.push, { detachHead: true }), + }, + async project => { + try { + await prepareInputAndOutput(inputDir, outputDir, gi); + } catch (e) { + const message = `Failed to prepare input and output for goal ${goalEvent.name}: ${e.message}`; + progressLog.write(message); + return { code: 1, message }; + } + const secrets = await prepareSecrets( + _.merge({}, registration.containers[0], (gi.parameters || {})["@atomist/sdm/secrets"] || {}), + gi, + ); + if (!!secrets?.files) { + for (const file of secrets.files) { + await fs.writeFile(file.mountPath, file.value); + } } - } - - goalEvent.state = SdmGoalState.in_process; - return goalEvent; - }); + goalEvent.state = SdmGoalState.in_process; + return goalEvent; + }, + ); } let containerName: string = _.get(registration, "containers[0].name"); @@ -542,8 +523,7 @@ export function executeK8sJob(): ExecuteGoal { ]; if (cacheEntriesToPut.length > 0) { try { - const project = GitCommandGitProject.fromBaseDir(id, projectDir, credentials, async () => { - }); + const project = GitCommandGitProject.fromBaseDir(id, projectDir, credentials, async () => {}); const cp = cachePut({ entries: cacheEntriesToPut.map(e => { // Prevent the type on the entry to get passed along when goal actually failed @@ -575,7 +555,8 @@ export function executeK8sJob(): ExecuteGoal { * goal. Otherwise, schedule the goal execution as a Kubernetes job * using [[scheduleK8sJob]]. */ -const containerExecutor: ExecuteGoal = gi => (process.env.ATOMIST_ISOLATED_GOAL) ? executeK8sJob()(gi) : scheduleK8sJob(gi); +const containerExecutor: ExecuteGoal = gi => + process.env.ATOMIST_ISOLATED_GOAL ? executeK8sJob()(gi) : scheduleK8sJob(gi); /** * Restore cache input entries before fulfilling goal. @@ -586,7 +567,9 @@ const containerFulfillerCacheRestore: GoalProjectListenerRegistration = { listener: async (project, gi) => { const data = goalData(gi.goalEvent); if (!data[ContainerRegistrationGoalDataKey]) { - throw new Error(`Goal ${gi.goal.uniqueName} has no Kubernetes container registration: ${gi.goalEvent.data}`); + throw new Error( + `Goal ${gi.goal.uniqueName} has no Kubernetes container registration: ${gi.goalEvent.data}`, + ); } const registration: K8sContainerRegistration = data[ContainerRegistrationGoalDataKey]; if (registration.input && registration.input.length > 0) { @@ -649,7 +632,7 @@ async function containerStarted(container: K8sContainer, attempts: number = 240) container.log.write(`Reading pod ${container.ns}/${container.pod} failed: ${k8sErrMsg(e)}`); continue; } - const containerStatus = pod.status.containerStatuses.find(c => c.name === container.name); + const containerStatus = pod.status?.containerStatuses?.find(c => c.name === container.name); if (containerStatus && (!!containerStatus.state?.running?.startedAt || !!containerStatus.state?.terminated)) { const message = `Container '${container.name}' started`; container.log.write(message); @@ -713,7 +696,14 @@ function containerWatch(container: K8sContainer, timeout: number): Promise { - const pod = obj as k8s.V1Pod; - if (pod?.status?.containerStatuses) { - const containerStatus = pod.status.containerStatuses.find(c => c.name === container.name); - if (containerStatus?.state?.terminated) { - const exitCode: number = containerStatus.state.terminated.exitCode; - if (exitCode === 0) { - podStatus = pod.status; - const msg = `Container '${container.name}' exited with status 0`; - container.log.write(msg); - if (logDone) { - containerCleanup(clean); - resolve(podStatus); - } - } else { - const msg = `Container '${container.name}' exited with status ${exitCode}`; - container.log.write(msg); - podError = new Error(msg); - (podError as any).podStatus = pod.status; - if (logDone) { - containerCleanup(clean); - reject(podError); + clean.watcher = await watch.watch( + watchPath, + {}, + async (phase, obj) => { + const pod = obj as k8s.V1Pod; + if (pod?.status?.containerStatuses) { + const containerStatus = pod.status.containerStatuses.find(c => c.name === container.name); + if (containerStatus?.state?.terminated) { + const exitCode: number = containerStatus.state.terminated.exitCode; + if (exitCode === 0) { + podStatus = pod.status; + const msg = `Container '${container.name}' exited with status 0`; + container.log.write(msg); + if (logDone) { + containerCleanup(clean); + resolve(podStatus); + } + } else { + const msg = `Container '${container.name}' exited with status ${exitCode}`; + container.log.write(msg); + podError = new Error(msg); + (podError as any).podStatus = pod.status; + if (logDone) { + containerCleanup(clean); + reject(podError); + } } + return; } - return; } - } - container.log.write(`Container '${container.name}' phase: ${phase}`); - }, err => { - err.message = `Container watcher failed: ${err.message}`; - container.log.write(err.message); - containerCleanup(clean); - reject(err); - }); + container.log.write(`Container '${container.name}' phase: ${phase}`); + }, + err => { + if (err) { + err.message = `Container watcher failed: ${err.message}`; + container.log.write(err.message); + containerCleanup(clean); + reject(err); + } else { + containerCleanup(clean); + } + }, + ); }); } diff --git a/lib/core/pack/k8s/kubernetes/application.ts b/lib/core/pack/k8s/kubernetes/application.ts index 7f215339b..f131c3571 100644 --- a/lib/core/pack/k8s/kubernetes/application.ts +++ b/lib/core/pack/k8s/kubernetes/application.ts @@ -16,26 +16,14 @@ import * as k8s from "@kubernetes/client-node"; import { k8sErrMsg } from "../support/error"; -import { - KubernetesClients, - makeApiClients, - makeNoOpApiClients, -} from "./clients"; +import { KubernetesClients, makeApiClients, makeNoOpApiClients } from "./clients"; import { loadKubeConfig } from "./config"; -import { - deleteAppResources, - DeleteAppResourcesArgCluster, - DeleteAppResourcesArgNamespaced, -} from "./delete"; +import { deleteAppResources, DeleteAppResourcesArgCluster, DeleteAppResourcesArgNamespaced } from "./delete"; import { upsertDeployment } from "./deployment"; import { upsertIngress } from "./ingress"; import { upsertNamespace } from "./namespace"; import { upsertRbac } from "./rbac"; -import { - KubernetesApplication, - KubernetesDelete, - reqString, -} from "./request"; +import { KubernetesApplication, KubernetesDelete, reqString } from "./request"; import { upsertSecrets } from "./secret"; import { upsertService } from "./service"; @@ -47,7 +35,10 @@ import { upsertService } from "./service"; * @param sdmFulfiller Registered name of the SDM fulfilling the deployment goal. * @return Array of resource specs upserted */ -export async function upsertApplication(app: KubernetesApplication, sdmFulfiller: string): Promise { +export async function upsertApplication( + app: KubernetesApplication, + sdmFulfiller: string, +): Promise { let clients: KubernetesClients; if (app.mode === "sync") { clients = makeNoOpApiClients(); @@ -66,7 +57,7 @@ export async function upsertApplication(app: KubernetesApplication, sdmFulfiller try { const k8sResources: k8s.KubernetesObject[] = []; k8sResources.push(await upsertNamespace(req)); - k8sResources.push(...Object.values(await upsertRbac(req) as any)); + k8sResources.push(...Object.values((await upsertRbac(req)) as any)); k8sResources.push(await upsertService(req)); k8sResources.push(...(await upsertSecrets(req))); k8sResources.push(await upsertDeployment(req)); @@ -100,13 +91,15 @@ export async function deleteApplication(del: KubernetesDelete): Promise | Omit> = [ + const resourceDeleters: Array< + Omit | Omit + > = [ { kind: "Ingress", namespaced: true, - api: req.clients.ext, - lister: req.clients.ext.listNamespacedIngress, - deleter: req.clients.ext.deleteNamespacedIngress, + api: req.clients.net, + lister: req.clients.net.listNamespacedIngress, + deleter: req.clients.net.deleteNamespacedIngress, }, { kind: "Deployment", diff --git a/lib/core/pack/k8s/kubernetes/clients.ts b/lib/core/pack/k8s/kubernetes/clients.ts index c78f3d2f4..264803fb1 100644 --- a/lib/core/pack/k8s/kubernetes/clients.ts +++ b/lib/core/pack/k8s/kubernetes/clients.ts @@ -25,8 +25,8 @@ export interface KubernetesClients { core: k8s.CoreV1Api; /** Kubernetes Apps client, GA in Kubernetes 1.9 */ apps: k8s.AppsV1Api; - /** Kubernetes Extension client */ - ext: k8s.ExtensionsV1beta1Api; + /** Kubernetes networking client */ + net: k8s.NetworkingV1beta1Api; /** Kubernetes RBAC client, GA in Kubernetes 1.8 */ rbac: k8s.RbacAuthorizationV1Api; } @@ -38,8 +38,8 @@ export function makeApiClients(kc: k8s.KubeConfig): KubernetesClients { const core = kc.makeApiClient(k8s.CoreV1Api); const apps = kc.makeApiClient(k8s.AppsV1Api); const rbac = kc.makeApiClient(k8s.RbacAuthorizationV1Api); - const ext = kc.makeApiClient(k8s.ExtensionsV1beta1Api); - return { core, apps, rbac, ext }; + const net = kc.makeApiClient(k8s.NetworkingV1beta1Api); + return { core, apps, net, rbac }; } /** @@ -47,7 +47,7 @@ export function makeApiClients(kc: k8s.KubeConfig): KubernetesClients { * sync repo. */ export function makeNoOpApiClients(): KubernetesClients { - const noop = async () => { }; + const noop = async () => {}; const core: any = { createNamespace: noop, deleteNamespace: noop, @@ -72,6 +72,12 @@ export function makeNoOpApiClients(): KubernetesClients { patchNamespacedDeployment: noop, readNamespacedDeployment: noop, }; + const net: any = { + createNamespacedIngress: noop, + deleteNamespacedIngress: noop, + patchNamespacedIngress: noop, + readNamespacedIngress: noop, + }; const rbac: any = { createClusterRole: noop, deleteClusterRole: noop, @@ -90,11 +96,5 @@ export function makeNoOpApiClients(): KubernetesClients { patchNamespacedRoleBinding: noop, readNamespacedRoleBinding: noop, }; - const ext: any = { - createNamespacedIngress: noop, - deleteNamespacedIngress: noop, - patchNamespacedIngress: noop, - readNamespacedIngress: noop, - }; - return { core, apps, rbac, ext }; + return { core, apps, net, rbac }; } diff --git a/lib/core/pack/k8s/kubernetes/delete.ts b/lib/core/pack/k8s/kubernetes/delete.ts index 555c58ca3..8e9660a96 100644 --- a/lib/core/pack/k8s/kubernetes/delete.ts +++ b/lib/core/pack/k8s/kubernetes/delete.ts @@ -18,17 +18,10 @@ import { logger } from "@atomist/automation-client/lib/util/logger"; import * as k8s from "@kubernetes/client-node"; import { k8sErrMsg } from "../support/error"; import { logRetry } from "../support/retry"; -import { - K8sDeleteResponse, - K8sListResponse, - K8sObjectApi, -} from "./api"; +import { K8sDeleteResponse, K8sListResponse, K8sObjectApi } from "./api"; import { loadKubeConfig } from "./config"; import { labelSelector } from "./labels"; -import { - appName, - KubernetesDeleteResourceRequest, -} from "./request"; +import { appName, KubernetesDeleteResourceRequest } from "./request"; import { logObject } from "./resource"; import { specSlug } from "./spec"; @@ -122,7 +115,7 @@ export interface DeleteAppResourcesArgBase { /** Delete request object. */ req: KubernetesDeleteResourceRequest; /** API object to use as `this` for lister and deleter. */ - api: k8s.CoreV1Api | k8s.AppsV1Api | k8s.ExtensionsV1beta1Api | k8s.RbacAuthorizationV1Api; + api: k8s.CoreV1Api | k8s.AppsV1Api | k8s.NetworkingV1beta1Api | k8s.RbacAuthorizationV1Api; /** Resource collection deleting function. */ lister: K8sNamespacedLister | K8sClusterLister; /** Resource collection deleting function. */ @@ -157,17 +150,25 @@ export async function deleteAppResources(arg: DeleteAppResourcesArg): Promise { - r.kind = r.kind || arg.kind; // list response does not include kind - return r; - })); + toDelete.push( + ...listResp.body.items.map(r => { + r.kind = r.kind || arg.kind; // list response does not include kind + return r; + }), + ); continu = listResp.body.metadata._continue; } while (!!continu); } catch (e) { @@ -177,12 +178,18 @@ export async function deleteAppResources(arg: DeleteAppResourcesArg): Promise { - const k8ventAnnot = stringify({ - webhooks: [`${webhookBaseUrl()}/atomist/kube/teams/${req.workspaceId}`], - }); const labels = applicationLabels(req); const matchers = matchLabels(req); const metadata = metadataTemplate({ @@ -87,9 +82,6 @@ export async function deploymentTemplate(req: KubernetesApplication & Kubernetes const podMetadata = metadataTemplate({ name: req.name, labels, - annotations: { - "atomist.com/k8vent": k8ventAnnot, - }, }); const selector: k8s.V1LabelSelector = { matchLabels: matchers, diff --git a/lib/core/pack/k8s/kubernetes/fetch.ts b/lib/core/pack/k8s/kubernetes/fetch.ts index dd45d391b..796234985 100644 --- a/lib/core/pack/k8s/kubernetes/fetch.ts +++ b/lib/core/pack/k8s/kubernetes/fetch.ts @@ -18,10 +18,7 @@ import * as k8s from "@kubernetes/client-node"; import * as _ from "lodash"; import { k8sErrMsg } from "../support/error"; import { K8sObjectApi } from "./api"; -import { - KubernetesClients, - makeApiClients, -} from "./clients"; +import { KubernetesClients, makeApiClients } from "./clients"; import { loadKubeConfig } from "./config"; import { labelMatch } from "./labels"; import { nameMatch } from "./name"; @@ -96,7 +93,7 @@ export const defaultKubernetesResourceSelectorKinds: KubernetesResourceKind[] = { apiVersion: "apps/v1", kind: "StatefulSet" }, { apiVersion: "autoscaling/v1", kind: "HorizontalPodAutoscaler" }, { apiVersion: "batch/v1beta1", kind: "CronJob" }, - { apiVersion: "extensions/v1beta1", kind: "Ingress" }, + { apiVersion: "networking.k8s.io/v1beta1", kind: "Ingress" }, { apiVersion: "networking.k8s.io/v1", kind: "NetworkPolicy" }, { apiVersion: "policy/v1beta1", kind: "PodDisruptionBudget" }, { apiVersion: "policy/v1beta1", kind: "PodSecurityPolicy" }, @@ -147,7 +144,10 @@ export const defaultKubernetesFetchOptions: KubernetesFetchOptions = { name: /^(?:cluster-admin(?:-binding)?|cloud-provider|kubernetes-dashboard)$/, }, { action: "exclude", kinds: [{ apiVersion: "storage.k8s.io/v1", kind: "StorageClass" }], name: "standard" }, - { action: "exclude", filter: (r: any) => r.kind === "Secret" && r.type === "kubernetes.io/service-account-token" }, + { + action: "exclude", + filter: (r: any) => r.kind === "Secret" && r.type === "kubernetes.io/service-account-token", + }, { action: "exclude", filter: r => /^ClusterRole/.test(r.kind) && /(?:kubelet|:)/.test(r.metadata.name) }, { action: "include", kinds: defaultKubernetesResourceSelectorKinds }, ], @@ -164,7 +164,9 @@ export const defaultKubernetesFetchOptions: KubernetesFetchOptions = { * @param options Kubernetes fetch options * @return Kubernetes resources matching the fetch options */ -export async function kubernetesFetch(options: KubernetesFetchOptions = defaultKubernetesFetchOptions): Promise { +export async function kubernetesFetch( + options: KubernetesFetchOptions = defaultKubernetesFetchOptions, +): Promise { let client: K8sObjectApi; let clients: KubernetesClients; try { @@ -229,14 +231,18 @@ export async function kubernetesFetch(options: KubernetesFetchOptions = defaultK * @param selectors Kubernetes resource selectors to ensure have default values * @return Properly defaulted Kubernetes resource selectors */ -export function populateResourceSelectorDefaults(selectors: KubernetesResourceSelector[]): KubernetesResourceSelector[] { - return selectors.map(s => { - const k: KubernetesResourceSelector = { action: "include", ...s }; - if (!k.kinds && k.action === "include") { - k.kinds = defaultKubernetesResourceSelectorKinds; - } - return k; - }).filter(s => s.action === "include" || s.filter || s.kinds || s.labelSelector || s.name || s.namespace); +export function populateResourceSelectorDefaults( + selectors: KubernetesResourceSelector[], +): KubernetesResourceSelector[] { + return selectors + .map(s => { + const k: KubernetesResourceSelector = { action: "include", ...s }; + if (!k.kinds && k.action === "include") { + k.kinds = defaultKubernetesResourceSelectorKinds; + } + return k; + }) + .filter(s => s.action === "include" || s.filter || s.kinds || s.labelSelector || s.name || s.namespace); } /** @@ -269,7 +275,10 @@ export function includedResourceKinds(selectors: KubernetesResourceSelector[]): * @param selectors All the resource selectors * @return A deduplicated array of Kubernetes cluster resource kinds among the inclusion rules */ -export async function clusterResourceKinds(selectors: KubernetesResourceSelector[], client: K8sObjectApi): Promise { +export async function clusterResourceKinds( + selectors: KubernetesResourceSelector[], + client: K8sObjectApi, +): Promise { const included = includedResourceKinds(selectors); const apiKinds: KubernetesResourceKind[] = []; for (const apiKind of included) { @@ -298,7 +307,6 @@ export async function namespaceResourceKinds( selectors: KubernetesResourceSelector[], client: K8sObjectApi, ): Promise { - const apiKinds: KubernetesResourceKind[] = []; for (const selector of selectors.filter(s => s.action === "include")) { if (nameMatch(ns, selector.namespace)) { @@ -381,7 +389,10 @@ export function cleanKubernetesSpec(obj: k8s.KubernetesObject, apiKind: Kubernet * @param selectors Filtering rules * @return Filtered array of Kubernetes resources */ -export function selectKubernetesResources(specs: k8s.KubernetesObject[], selectors: KubernetesResourceSelector[]): k8s.KubernetesObject[] { +export function selectKubernetesResources( + specs: k8s.KubernetesObject[], + selectors: KubernetesResourceSelector[], +): k8s.KubernetesObject[] { const uniqueSpecs = _.uniqBy(specs, kubernetesResourceIdentity); if (!selectors || selectors.length < 1) { return uniqueSpecs; @@ -423,7 +434,10 @@ export function kubernetesResourceIdentity(obj: k8s.KubernetesObject): string { * @param selector Selector to use for checking * @return Selector action if there is a match, `undefined` otherwise */ -export function selectorMatch(spec: k8s.KubernetesObject, selector: KubernetesResourceSelector): "include" | "exclude" | undefined { +export function selectorMatch( + spec: k8s.KubernetesObject, + selector: KubernetesResourceSelector, +): "include" | "exclude" | undefined { if (!nameMatch(spec.metadata.name, selector.name)) { return undefined; } diff --git a/lib/core/pack/k8s/kubernetes/ingress.ts b/lib/core/pack/k8s/kubernetes/ingress.ts index cb0caae73..4d3c050d3 100644 --- a/lib/core/pack/k8s/kubernetes/ingress.ts +++ b/lib/core/pack/k8s/kubernetes/ingress.ts @@ -22,12 +22,7 @@ import { logRetry } from "../support/retry"; import { applicationLabels } from "./labels"; import { metadataTemplate } from "./metadata"; import { patchHeaders } from "./patch"; -import { - appName, - KubernetesApplication, - KubernetesResourceRequest, - KubernetesSdm, -} from "./request"; +import { appName, KubernetesApplication, KubernetesResourceRequest, KubernetesSdm } from "./request"; import { logObject } from "./resource"; /** @@ -50,16 +45,31 @@ export async function upsertIngress(req: KubernetesResourceRequest): Promise req.clients.ext.createNamespacedIngress(spec.metadata.namespace, spec), `create ingress ${slug}`); + await logRetry( + () => req.clients.net.createNamespacedIngress(spec.metadata.namespace, spec), + `create ingress ${slug}`, + ); return spec; } logger.info(`Ingress ${slug} exists, patching using '${logObject(spec)}'`); - await logRetry(() => req.clients.ext.patchNamespacedIngress(spec.metadata.name, spec.metadata.namespace, spec, - undefined, undefined, undefined, undefined, patchHeaders(req)), `patch ingress ${slug}`); + await logRetry( + () => + req.clients.net.patchNamespacedIngress( + spec.metadata.name, + spec.metadata.namespace, + spec, + undefined, + undefined, + undefined, + undefined, + patchHeaders(req), + ), + `patch ingress ${slug}`, + ); return spec; } @@ -74,7 +84,7 @@ function httpIngressPath(req: KubernetesApplication): k8s.NetworkingV1beta1HTTPI path: req.path, backend: { serviceName: req.name, - servicePort: "http" as any as object, + servicePort: ("http" as any) as object, }, }; return httpPath; @@ -92,7 +102,9 @@ function httpIngressPath(req: KubernetesApplication): k8s.NetworkingV1beta1HTTPI * @param req Kubernestes application * @return ingress spec with single rule */ -export async function ingressTemplate(req: KubernetesApplication & KubernetesSdm): Promise { +export async function ingressTemplate( + req: KubernetesApplication & KubernetesSdm, +): Promise { const labels = applicationLabels(req); const metadata = metadataTemplate({ name: req.name, @@ -105,7 +117,7 @@ export async function ingressTemplate(req: KubernetesApplication & KubernetesSdm paths: [httpPath], }, } as any; - const apiVersion = "extensions/v1beta1"; + const apiVersion = "networking.k8s.io/v1beta1"; const kind = "Ingress"; const i: k8s.NetworkingV1beta1Ingress = { apiVersion, diff --git a/lib/core/pack/k8s/kubernetes/resource.ts b/lib/core/pack/k8s/kubernetes/resource.ts index 75b8b4bf2..5b443423e 100644 --- a/lib/core/pack/k8s/kubernetes/resource.ts +++ b/lib/core/pack/k8s/kubernetes/resource.ts @@ -50,7 +50,7 @@ export function appObject(app: KubernetesDelete, kind: string): k8s.KubernetesOb ko.apiVersion = "apps/v1"; break; case "Ingress": - ko.apiVersion = "extensions/v1beta1"; + ko.apiVersion = "networking.k8s.io/v1beta1"; break; case "ClusterRole": case "ClusterRoleBinding": diff --git a/test/core/pack/k8s/kubernetes/api.test.ts b/test/core/pack/k8s/kubernetes/api.test.ts index 0573c402d..c1e6d9b64 100644 --- a/test/core/pack/k8s/kubernetes/api.test.ts +++ b/test/core/pack/k8s/kubernetes/api.test.ts @@ -17,27 +17,16 @@ import { execPromise } from "@atomist/automation-client/lib/util/child_process"; import * as k8s from "@kubernetes/client-node"; import * as assert from "power-assert"; -import { - appendName, - K8sObjectApi, - namespaceRequired, -} from "../../../../../lib/core/pack/k8s/kubernetes/api"; +import { appendName, K8sObjectApi, namespaceRequired } from "../../../../../lib/core/pack/k8s/kubernetes/api"; import { applySpec } from "../../../../../lib/core/pack/k8s/kubernetes/apply"; import { loadKubeConfig } from "../../../../../lib/core/pack/k8s/kubernetes/config"; import { deleteSpec } from "../../../../../lib/core/pack/k8s/kubernetes/delete"; -import { - afterRetry, - beforeRetry, - k8sAvailable, - rng, -} from "../k8s"; +import { afterRetry, beforeRetry, k8sAvailable, rng } from "../k8s"; /* tslint:disable:max-file-line-count */ describe("pack/k8s/kubernetes/api", () => { - describe("appendName", () => { - it("should return append name", () => { ["delete", "patch", "read", "replace"].forEach((a: any) => { assert(appendName(a)); @@ -49,11 +38,9 @@ describe("pack/k8s/kubernetes/api", () => { assert(!appendName(a)); }); }); - }); describe("namespaceRequired", () => { - it("should return namespace required", () => { const r: any = { namespaced: true }; ["create", "delete", "patch", "read", "replace"].forEach((a: any) => { @@ -72,15 +59,13 @@ describe("pack/k8s/kubernetes/api", () => { assert(!namespaceRequired(r, a)); }); }); - }); - describe("integration", function(this: Mocha.Suite): void { - + describe("integration", function (this: Mocha.Suite): void { this.timeout(5000); - before(async function(this: Mocha.Context): Promise { - if (!await k8sAvailable()) { + before(async function (this: Mocha.Context): Promise { + if (!(await k8sAvailable())) { this.skip(); } beforeRetry(); @@ -90,9 +75,8 @@ describe("pack/k8s/kubernetes/api", () => { }); describe("K8sObjectApi.specUriPath", () => { - let client: K8sObjectApi; - before(function(this: Mocha.Context): void { + before(function (this: Mocha.Context): void { try { const kc = loadKubeConfig(); client = kc.makeApiClient(K8sObjectApi); @@ -189,7 +173,7 @@ describe("pack/k8s/kubernetes/api", () => { it("should return properly pluralize", async () => { const o = { - apiVersion: "extensions/v1beta1", + apiVersion: "networking.k8s.io/v1beta1", kind: "Ingress", metadata: { name: "repeater", @@ -197,25 +181,85 @@ describe("pack/k8s/kubernetes/api", () => { }, }; const r = await client.specUriPath(o, "delete"); - assert(r === "apis/extensions/v1beta1/namespaces/fugazi/ingresses/repeater"); + assert(r === "apis/networking.k8s.io/v1beta1/namespaces/fugazi/ingresses/repeater"); }); it("should handle a variety of resources", async () => { /* tslint:disable:max-line-length */ const a = [ { apiVersion: "v1", kind: "Service", ns: true, e: "api/v1/namespaces/fugazi/services/repeater" }, - { apiVersion: "v1", kind: "ServiceAccount", ns: true, e: "api/v1/namespaces/fugazi/serviceaccounts/repeater" }, - { apiVersion: "rbac.authorization.k8s.io/v1", kind: "Role", ns: true, e: "apis/rbac.authorization.k8s.io/v1/namespaces/fugazi/roles/repeater" }, - { apiVersion: "rbac.authorization.k8s.io/v1", kind: "ClusterRole", ns: false, e: "apis/rbac.authorization.k8s.io/v1/clusterroles/repeater" }, - { apiVersion: "extensions/v1beta1", kind: "NetworkPolicy", ns: true, e: "apis/extensions/v1beta1/namespaces/fugazi/networkpolicies/repeater" }, - { apiVersion: "networking.k8s.io/v1", kind: "NetworkPolicy", ns: true, e: "apis/networking.k8s.io/v1/namespaces/fugazi/networkpolicies/repeater" }, - { apiVersion: "extensions/v1beta1", kind: "Ingress", ns: true, e: "apis/extensions/v1beta1/namespaces/fugazi/ingresses/repeater" }, - { apiVersion: "extensions/v1beta1", kind: "DaemonSet", ns: true, e: "apis/extensions/v1beta1/namespaces/fugazi/daemonsets/repeater" }, - { apiVersion: "extensions/v1beta1", kind: "DaemonSet", ns: true, e: "apis/extensions/v1beta1/namespaces/fugazi/daemonsets/repeater" }, - { apiVersion: "apps/v1", kind: "DaemonSet", ns: true, e: "apis/apps/v1/namespaces/fugazi/daemonsets/repeater" }, - { apiVersion: "extensions/v1beta1", kind: "Deployment", ns: true, e: "apis/extensions/v1beta1/namespaces/fugazi/deployments/repeater" }, - { apiVersion: "apps/v1", kind: "Deployment", ns: true, e: "apis/apps/v1/namespaces/fugazi/deployments/repeater" }, - { apiVersion: "storage.k8s.io/v1", kind: "StorageClass", ns: false, e: "apis/storage.k8s.io/v1/storageclasses/repeater" }, + { + apiVersion: "v1", + kind: "ServiceAccount", + ns: true, + e: "api/v1/namespaces/fugazi/serviceaccounts/repeater", + }, + { + apiVersion: "rbac.authorization.k8s.io/v1", + kind: "Role", + ns: true, + e: "apis/rbac.authorization.k8s.io/v1/namespaces/fugazi/roles/repeater", + }, + { + apiVersion: "rbac.authorization.k8s.io/v1", + kind: "ClusterRole", + ns: false, + e: "apis/rbac.authorization.k8s.io/v1/clusterroles/repeater", + }, + { + apiVersion: "networking.k8s.io/v1", + kind: "NetworkPolicy", + ns: true, + e: "apis/networking.k8s.io/v1/namespaces/fugazi/networkpolicies/repeater", + }, + { + apiVersion: "networking.k8s.io/v1", + kind: "NetworkPolicy", + ns: true, + e: "apis/networking.k8s.io/v1/namespaces/fugazi/networkpolicies/repeater", + }, + { + apiVersion: "networking.k8s.io/v1beta1", + kind: "Ingress", + ns: true, + e: "apis/networking.k8s.io/v1beta1/namespaces/fugazi/ingresses/repeater", + }, + { + apiVersion: "apps/v1", + kind: "DaemonSet", + ns: true, + e: "apis/apps/v1/namespaces/fugazi/daemonsets/repeater", + }, + { + apiVersion: "apps/v1", + kind: "DaemonSet", + ns: true, + e: "apis/apps/v1/namespaces/fugazi/daemonsets/repeater", + }, + { + apiVersion: "apps/v1", + kind: "DaemonSet", + ns: true, + e: "apis/apps/v1/namespaces/fugazi/daemonsets/repeater", + }, + { + apiVersion: "apps/v1", + kind: "Deployment", + ns: true, + e: "apis/apps/v1/namespaces/fugazi/deployments/repeater", + }, + { + apiVersion: "apps/v1", + kind: "Deployment", + ns: true, + e: "apis/apps/v1/namespaces/fugazi/deployments/repeater", + }, + { + apiVersion: "storage.k8s.io/v1", + kind: "StorageClass", + ns: false, + e: "apis/storage.k8s.io/v1/storageclasses/repeater", + }, ]; /* tslint:enable:max-line-length */ for (const k of a) { @@ -238,18 +282,78 @@ describe("pack/k8s/kubernetes/api", () => { /* tslint:disable:max-line-length */ const a = [ { apiVersion: "v1", kind: "Service", ns: true, e: "api/v1/namespaces/fugazi/services" }, - { apiVersion: "v1", kind: "ServiceAccount", ns: true, e: "api/v1/namespaces/fugazi/serviceaccounts" }, - { apiVersion: "rbac.authorization.k8s.io/v1", kind: "Role", ns: true, e: "apis/rbac.authorization.k8s.io/v1/namespaces/fugazi/roles" }, - { apiVersion: "rbac.authorization.k8s.io/v1", kind: "ClusterRole", ns: false, e: "apis/rbac.authorization.k8s.io/v1/clusterroles" }, - { apiVersion: "extensions/v1beta1", kind: "NetworkPolicy", ns: true, e: "apis/extensions/v1beta1/namespaces/fugazi/networkpolicies" }, - { apiVersion: "networking.k8s.io/v1", kind: "NetworkPolicy", ns: true, e: "apis/networking.k8s.io/v1/namespaces/fugazi/networkpolicies" }, - { apiVersion: "extensions/v1beta1", kind: "Ingress", ns: true, e: "apis/extensions/v1beta1/namespaces/fugazi/ingresses" }, - { apiVersion: "extensions/v1beta1", kind: "DaemonSet", ns: true, e: "apis/extensions/v1beta1/namespaces/fugazi/daemonsets" }, - { apiVersion: "extensions/v1beta1", kind: "DaemonSet", ns: true, e: "apis/extensions/v1beta1/namespaces/fugazi/daemonsets" }, - { apiVersion: "apps/v1", kind: "DaemonSet", ns: true, e: "apis/apps/v1/namespaces/fugazi/daemonsets" }, - { apiVersion: "extensions/v1beta1", kind: "Deployment", ns: true, e: "apis/extensions/v1beta1/namespaces/fugazi/deployments" }, - { apiVersion: "apps/v1", kind: "Deployment", ns: true, e: "apis/apps/v1/namespaces/fugazi/deployments" }, - { apiVersion: "storage.k8s.io/v1", kind: "StorageClass", ns: false, e: "apis/storage.k8s.io/v1/storageclasses" }, + { + apiVersion: "v1", + kind: "ServiceAccount", + ns: true, + e: "api/v1/namespaces/fugazi/serviceaccounts", + }, + { + apiVersion: "rbac.authorization.k8s.io/v1", + kind: "Role", + ns: true, + e: "apis/rbac.authorization.k8s.io/v1/namespaces/fugazi/roles", + }, + { + apiVersion: "rbac.authorization.k8s.io/v1", + kind: "ClusterRole", + ns: false, + e: "apis/rbac.authorization.k8s.io/v1/clusterroles", + }, + { + apiVersion: "networking.k8s.io/v1", + kind: "NetworkPolicy", + ns: true, + e: "apis/networking.k8s.io/v1/namespaces/fugazi/networkpolicies", + }, + { + apiVersion: "networking.k8s.io/v1", + kind: "NetworkPolicy", + ns: true, + e: "apis/networking.k8s.io/v1/namespaces/fugazi/networkpolicies", + }, + { + apiVersion: "networking.k8s.io/v1beta1", + kind: "Ingress", + ns: true, + e: "apis/networking.k8s.io/v1beta1/namespaces/fugazi/ingresses", + }, + { + apiVersion: "apps/v1", + kind: "DaemonSet", + ns: true, + e: "apis/apps/v1/namespaces/fugazi/daemonsets", + }, + { + apiVersion: "apps/v1", + kind: "DaemonSet", + ns: true, + e: "apis/apps/v1/namespaces/fugazi/daemonsets", + }, + { + apiVersion: "apps/v1", + kind: "DaemonSet", + ns: true, + e: "apis/apps/v1/namespaces/fugazi/daemonsets", + }, + { + apiVersion: "apps/v1", + kind: "Deployment", + ns: true, + e: "apis/apps/v1/namespaces/fugazi/deployments", + }, + { + apiVersion: "apps/v1", + kind: "Deployment", + ns: true, + e: "apis/apps/v1/namespaces/fugazi/deployments", + }, + { + apiVersion: "storage.k8s.io/v1", + kind: "StorageClass", + ns: false, + e: "apis/storage.k8s.io/v1/storageclasses", + }, ]; /* tslint:enable:max-line-length */ for (const k of a) { @@ -322,13 +426,11 @@ describe("pack/k8s/kubernetes/api", () => { } assert(thrown, "no error thrown"); }); - }); describe("baseRequestOptions", () => { - let client: K8sObjectApi; - before(function(this: Mocha.Context): void { + before(function (this: Mocha.Context): void { try { const kc = loadKubeConfig(); client = kc.makeApiClient(K8sObjectApi); @@ -366,16 +468,16 @@ describe("pack/k8s/kubernetes/api", () => { }); it("should override patch content-type header", async () => { - const o = client.baseRequestOptions("PATCH", { headers: { "Content-Type": "application/merge-patch+json" } }); + const o = client.baseRequestOptions("PATCH", { + headers: { "Content-Type": "application/merge-patch+json" }, + }); assert(o.method === "PATCH"); assert((o.uri as string).endsWith("/")); assert(o.headers["Content-Type"] === "application/merge-patch+json"); }); - }); describe("apply & delete", () => { - it("should apply and delete resources", async () => { const s = { apiVersion: "v1", @@ -476,7 +578,11 @@ describe("pack/k8s/kubernetes/api", () => { await applySpec(s); } catch (e) { thrown = true; - assert(/Service "_not_a_valid_name_" is invalid: metadata.name: Invalid value: "_not_a_valid_name_":/.test(e.message)); + assert( + /Service "_not_a_valid_name_" is invalid: metadata.name: Invalid value: "_not_a_valid_name_":/.test( + e.message, + ), + ); } assert(thrown, "error not thrown"); const d = { @@ -517,13 +623,14 @@ describe("pack/k8s/kubernetes/api", () => { await applySpec(d); } catch (e) { thrown = true; - assert((e.message as string).startsWith("Failed to fetch resource metadata for applications/v1/Deployment: ")); + assert( + (e.message as string).startsWith( + "Failed to fetch resource metadata for applications/v1/Deployment: ", + ), + ); } assert(thrown, "error not thrown"); }); - }); - }); - }); diff --git a/test/core/pack/k8s/kubernetes/clients.test.ts b/test/core/pack/k8s/kubernetes/clients.test.ts index e6773b6f3..c8d6faf5a 100644 --- a/test/core/pack/k8s/kubernetes/clients.test.ts +++ b/test/core/pack/k8s/kubernetes/clients.test.ts @@ -18,18 +18,14 @@ import * as assert from "power-assert"; import { makeNoOpApiClients } from "../../../../../lib/core/pack/k8s/kubernetes/clients"; describe("pack/k8s/kubernetes/clients", () => { - describe("makeNoOpApiClients", () => { - it("should make the no-op clients", () => { const c = makeNoOpApiClients(); assert(c); assert(c.core); assert(c.apps); + assert(c.net); assert(c.rbac); - assert(c.ext); }); - }); - }); diff --git a/test/core/pack/k8s/kubernetes/deployment.test.ts b/test/core/pack/k8s/kubernetes/deployment.test.ts index 9041600ad..c3f36f792 100644 --- a/test/core/pack/k8s/kubernetes/deployment.test.ts +++ b/test/core/pack/k8s/kubernetes/deployment.test.ts @@ -46,8 +46,6 @@ describe("core/pack/k8s/kubernetes/deployment", () => { assert(d.metadata.labels["atomist.com/workspaceId"] === r.workspaceId); assert(d.spec.selector.matchLabels["app.kubernetes.io/name"] === r.name); assert(d.spec.selector.matchLabels["atomist.com/workspaceId"] === r.workspaceId); - assert(d.spec.template.metadata.annotations["atomist.com/k8vent"] === - `{"webhooks":["https://webhook.atomist.com/atomist/kube/teams/KAT3BU5H"]}`); assert(d.spec.template.metadata.labels["app.kubernetes.io/name"] === r.name); assert(d.spec.template.metadata.labels["app.kubernetes.io/part-of"] === r.name); assert(d.spec.template.metadata.labels["app.kubernetes.io/managed-by"] === r.sdmFulfiller); @@ -126,9 +124,6 @@ describe("core/pack/k8s/kubernetes/deployment", () => { "app.kubernetes.io/part-of": r.name, "atomist.com/workspaceId": r.workspaceId, }, - annotations: { - "atomist.com/k8vent": `{"webhooks":["https://webhook.atomist.com/atomist/kube/teams/${r.workspaceId}"]}`, - }, }, spec: { containers: [ @@ -232,9 +227,6 @@ describe("core/pack/k8s/kubernetes/deployment", () => { "app.kubernetes.io/part-of": r.name, "atomist.com/workspaceId": r.workspaceId, }, - annotations: { - "atomist.com/k8vent": `{"webhooks":["https://webhook.atomist.com/atomist/kube/teams/${r.workspaceId}"]}`, - }, }, spec: { containers: [ @@ -332,9 +324,6 @@ describe("core/pack/k8s/kubernetes/deployment", () => { "app.kubernetes.io/part-of": r.name, "atomist.com/workspaceId": r.workspaceId, }, - annotations: { - "atomist.com/k8vent": `{"webhooks":["https://webhook.atomist.com/atomist/kube/teams/${r.workspaceId}"]}`, - }, }, spec: { containers: [ @@ -426,9 +415,6 @@ describe("core/pack/k8s/kubernetes/deployment", () => { "app.kubernetes.io/part-of": r.name, "atomist.com/workspaceId": r.workspaceId, }, - annotations: { - "atomist.com/k8vent": `{"webhooks":["https://webhook.atomist.com/atomist/kube/teams/${r.workspaceId}"]}`, - }, }, spec: { containers: [ @@ -514,9 +500,6 @@ describe("core/pack/k8s/kubernetes/deployment", () => { "app.kubernetes.io/part-of": r.name, "atomist.com/workspaceId": r.workspaceId, }, - annotations: { - "atomist.com/k8vent": `{"webhooks":["https://webhook.atomist.com/atomist/kube/teams/${r.workspaceId}"]}`, - }, }, spec: { containers: [ diff --git a/test/core/pack/k8s/kubernetes/fetch.test.ts b/test/core/pack/k8s/kubernetes/fetch.test.ts index 36f884b5b..7165ef3bc 100644 --- a/test/core/pack/k8s/kubernetes/fetch.test.ts +++ b/test/core/pack/k8s/kubernetes/fetch.test.ts @@ -33,16 +33,11 @@ import { selectKubernetesResources, selectorMatch, } from "../../../../../lib/core/pack/k8s/kubernetes/fetch"; -import { - afterRetry, - beforeRetry, - k8sAvailable, -} from "../k8s"; +import { afterRetry, beforeRetry, k8sAvailable } from "../k8s"; /* tslint:disable:max-file-line-count */ describe("pack/k8s/kubernetes/fetch", () => { - const client: any = { resource: async (apiVersion: string, kind: string): Promise => { const clusterResources = [ @@ -83,7 +78,6 @@ describe("pack/k8s/kubernetes/fetch", () => { }; describe("populateResourceSelectorDefaults", () => { - it("should do nothing successfully", () => { const p = populateResourceSelectorDefaults([]); assert.deepStrictEqual(p, []); @@ -108,7 +102,7 @@ describe("pack/k8s/kubernetes/fetch", () => { { apiVersion: "apps/v1", kind: "StatefulSet" }, { apiVersion: "autoscaling/v1", kind: "HorizontalPodAutoscaler" }, { apiVersion: "batch/v1beta1", kind: "CronJob" }, - { apiVersion: "extensions/v1beta1", kind: "Ingress" }, + { apiVersion: "networking.k8s.io/v1beta1", kind: "Ingress" }, { apiVersion: "networking.k8s.io/v1", kind: "NetworkPolicy" }, { apiVersion: "policy/v1beta1", kind: "PodDisruptionBudget" }, { apiVersion: "policy/v1beta1", kind: "PodSecurityPolicy" }, @@ -138,9 +132,7 @@ describe("pack/k8s/kubernetes/fetch", () => { const e = [ { action: "include", - kinds: [ - { apiVersion: "networking.k8s.io/v1", kind: "NetworkPolicy" }, - ], + kinds: [{ apiVersion: "networking.k8s.io/v1", kind: "NetworkPolicy" }], }, ]; assert.deepStrictEqual(p, e); @@ -202,7 +194,7 @@ describe("pack/k8s/kubernetes/fetch", () => { { apiVersion: "apps/v1", kind: "StatefulSet" }, { apiVersion: "autoscaling/v1", kind: "HorizontalPodAutoscaler" }, { apiVersion: "batch/v1beta1", kind: "CronJob" }, - { apiVersion: "extensions/v1beta1", kind: "Ingress" }, + { apiVersion: "networking.k8s.io/v1beta1", kind: "Ingress" }, { apiVersion: "networking.k8s.io/v1", kind: "NetworkPolicy" }, { apiVersion: "policy/v1beta1", kind: "PodDisruptionBudget" }, { apiVersion: "policy/v1beta1", kind: "PodSecurityPolicy" }, @@ -273,11 +265,9 @@ describe("pack/k8s/kubernetes/fetch", () => { ]; assert.deepStrictEqual(p, e); }); - }); describe("includedResourceKinds", () => { - it("should find nothing successfully", () => { const s = includedResourceKinds([]); assert.deepStrictEqual(s, []); @@ -301,7 +291,7 @@ describe("pack/k8s/kubernetes/fetch", () => { { apiVersion: "apps/v1", kind: "DaemonSet" }, { apiVersion: "apps/v1", kind: "Deployment" }, { apiVersion: "apps/v1", kind: "StatefulSet" }, - { apiVersion: "extensions/v1beta1", kind: "Ingress" }, + { apiVersion: "networking.k8s.io/v1beta1", kind: "Ingress" }, ], }, { @@ -325,7 +315,7 @@ describe("pack/k8s/kubernetes/fetch", () => { { apiVersion: "apps/v1", kind: "DaemonSet" }, { apiVersion: "apps/v1", kind: "Deployment" }, { apiVersion: "apps/v1", kind: "StatefulSet" }, - { apiVersion: "extensions/v1beta1", kind: "Ingress" }, + { apiVersion: "networking.k8s.io/v1beta1", kind: "Ingress" }, { apiVersion: "autoscaling/v1", kind: "HorizontalPodAutoscaler" }, { apiVersion: "batch/v1beta1", kind: "CronJob" }, { apiVersion: "networking.k8s.io/v1", kind: "NetworkPolicy" }, @@ -353,7 +343,7 @@ describe("pack/k8s/kubernetes/fetch", () => { { apiVersion: "apps/v1", kind: "DaemonSet" }, { apiVersion: "apps/v1", kind: "Deployment" }, { apiVersion: "apps/v1", kind: "StatefulSet" }, - { apiVersion: "extensions/v1beta1", kind: "Ingress" }, + { apiVersion: "networking.k8s.io/v1beta1", kind: "Ingress" }, ], }, { @@ -384,7 +374,7 @@ describe("pack/k8s/kubernetes/fetch", () => { { apiVersion: "apps/v1", kind: "DaemonSet" }, { apiVersion: "apps/v1", kind: "Deployment" }, { apiVersion: "apps/v1", kind: "StatefulSet" }, - { apiVersion: "extensions/v1beta1", kind: "Ingress" }, + { apiVersion: "networking.k8s.io/v1beta1", kind: "Ingress" }, { apiVersion: "autoscaling/v1", kind: "HorizontalPodAutoscaler" }, { apiVersion: "batch/v1beta1", kind: "CronJob" }, { apiVersion: "networking.k8s.io/v1", kind: "NetworkPolicy" }, @@ -412,7 +402,7 @@ describe("pack/k8s/kubernetes/fetch", () => { { apiVersion: "apps/v1", kind: "DaemonSet" }, { apiVersion: "apps/v1", kind: "Deployment" }, { apiVersion: "apps/v1", kind: "StatefulSet" }, - { apiVersion: "extensions/v1beta1", kind: "Ingress" }, + { apiVersion: "networking.k8s.io/v1beta1", kind: "Ingress" }, ], }, { @@ -448,7 +438,7 @@ describe("pack/k8s/kubernetes/fetch", () => { { apiVersion: "apps/v1", kind: "DaemonSet" }, { apiVersion: "apps/v1", kind: "Deployment" }, { apiVersion: "apps/v1", kind: "StatefulSet" }, - { apiVersion: "extensions/v1beta1", kind: "Ingress" }, + { apiVersion: "networking.k8s.io/v1beta1", kind: "Ingress" }, ], }, { @@ -514,11 +504,9 @@ describe("pack/k8s/kubernetes/fetch", () => { ]; assert.deepStrictEqual(c, e); }); - }); describe("clusterResourceKinds", () => { - it("should return empty array if no cluster resources", async () => { const s: KubernetesResourceSelector[] = [ { @@ -537,7 +525,7 @@ describe("pack/k8s/kubernetes/fetch", () => { { apiVersion: "apps/v1", kind: "DaemonSet" }, { apiVersion: "apps/v1", kind: "Deployment" }, { apiVersion: "apps/v1", kind: "StatefulSet" }, - { apiVersion: "extensions/v1beta1", kind: "Ingress" }, + { apiVersion: "networking.k8s.io/v1beta1", kind: "Ingress" }, ], }, { @@ -563,7 +551,9 @@ describe("pack/k8s/kubernetes/fetch", () => { }); it("should return cluster resources from default", async () => { - const s: KubernetesResourceSelector[] = [{ action: "include", kinds: defaultKubernetesResourceSelectorKinds }]; + const s: KubernetesResourceSelector[] = [ + { action: "include", kinds: defaultKubernetesResourceSelectorKinds }, + ]; const c = await clusterResourceKinds(s, client); const e = [ { apiVersion: "v1", kind: "Namespace" }, @@ -595,15 +585,15 @@ describe("pack/k8s/kubernetes/fetch", () => { }); it("should return nothing from exclude", async () => { - const s: KubernetesResourceSelector[] = [{ action: "exclude", kinds: defaultKubernetesResourceSelectorKinds }]; + const s: KubernetesResourceSelector[] = [ + { action: "exclude", kinds: defaultKubernetesResourceSelectorKinds }, + ]; const c = await clusterResourceKinds(s, client); assert.deepStrictEqual(c, []); }); - }); describe("namespaceResourceKinds", async () => { - it("should find nothing successfully", async () => { const n = "son-house"; const s = await namespaceResourceKinds(n, [], client); @@ -717,7 +707,7 @@ describe("pack/k8s/kubernetes/fetch", () => { { apiVersion: "apps/v1", kind: "DaemonSet" }, { apiVersion: "apps/v1", kind: "Deployment" }, { apiVersion: "apps/v1", kind: "StatefulSet" }, - { apiVersion: "extensions/v1beta1", kind: "Ingress" }, + { apiVersion: "networking.k8s.io/v1beta1", kind: "Ingress" }, ], namespace: "son-house", }, @@ -751,7 +741,7 @@ describe("pack/k8s/kubernetes/fetch", () => { { apiVersion: "apps/v1", kind: "DaemonSet" }, { apiVersion: "apps/v1", kind: "Deployment" }, { apiVersion: "apps/v1", kind: "StatefulSet" }, - { apiVersion: "extensions/v1beta1", kind: "Ingress" }, + { apiVersion: "networking.k8s.io/v1beta1", kind: "Ingress" }, { apiVersion: "autoscaling/v1", kind: "HorizontalPodAutoscaler" }, { apiVersion: "batch/v1beta1", kind: "CronJob" }, { apiVersion: "networking.k8s.io/v1", kind: "NetworkPolicy" }, @@ -821,11 +811,9 @@ describe("pack/k8s/kubernetes/fetch", () => { const c = await namespaceResourceKinds(n, s, client); assert.deepStrictEqual(c, []); }); - }); describe("cleanKubernetesSpec", () => { - it("should do nothing safely", () => { const c = cleanKubernetesSpec(undefined, { apiVersion: "v1", kind: "Secret" }); assert(c === undefined); @@ -896,10 +884,12 @@ describe("pack/k8s/kubernetes/fetch", () => { }, }, spec: { - containers: [{ - image: "raphaelsaadiq/the-way-i-see-it:2008", - name: "the-way-i-see-it", - }], + containers: [ + { + image: "raphaelsaadiq/the-way-i-see-it:2008", + name: "the-way-i-see-it", + }, + ], }, }, }, @@ -965,10 +955,12 @@ describe("pack/k8s/kubernetes/fetch", () => { }, }, spec: { - containers: [{ - image: "raphaelsaadiq/the-way-i-see-it:2008", - name: "the-way-i-see-it", - }], + containers: [ + { + image: "raphaelsaadiq/the-way-i-see-it:2008", + name: "the-way-i-see-it", + }, + ], }, }, }, @@ -1039,11 +1031,9 @@ describe("pack/k8s/kubernetes/fetch", () => { }; assert.deepStrictEqual(c, e); }); - }); describe("selectKubernetesResources", () => { - it("should do nothing successfully", () => { const r: k8s.KubernetesObject[] = []; const s: KubernetesResourceSelector[] = []; @@ -1097,7 +1087,11 @@ describe("pack/k8s/kubernetes/fetch", () => { { kind: "DaemonSet", metadata: { name: "sunny-afternoon", namespace: "face2face" } }, { kind: "DaemonSet", metadata: { name: "kube-proxy", namespace: "kube-system" } }, { kind: "DaemonSet", metadata: { name: "sunny-afternoon", namespace: "face-to-face" } }, - { kind: "Secret", metadata: { name: "default-token-12345", namespace: "default" }, type: "kubernetes.io/service-account-token" }, + { + kind: "Secret", + metadata: { name: "default-token-12345", namespace: "default" }, + type: "kubernetes.io/service-account-token", + }, { kind: "Service", metadata: { name: "tired-of-waiting-for-you", namespace: "kinda-kinks" } }, { kind: "Service", metadata: { name: "kubernetes", namespace: "default" } }, { kind: "Service", metadata: { name: "kubernetes", namespace: "nondefault" } }, @@ -1138,7 +1132,11 @@ describe("pack/k8s/kubernetes/fetch", () => { website: "https://thekinks.info/", }; const r = [ - { apiVersion: "v1", kind: "Secret", metadata: { name: "you-really-got-me", namespace: "kinks", labels } }, + { + apiVersion: "v1", + kind: "Secret", + metadata: { name: "you-really-got-me", namespace: "kinks", labels }, + }, { kind: "Deployment", metadata: { name: "waterloo-sunset", namespace: "something-else", labels } }, { kind: "Deployment", metadata: { name: "waterloo-sunset-mono", namespace: "something-else", labels } }, { kind: "DaemonSet", metadata: { name: "sunny-afternoon", namespace: "face2face", labels } }, @@ -1147,16 +1145,29 @@ describe("pack/k8s/kubernetes/fetch", () => { { kind: "Service", metadata: { name: "waterloo-sunset", namespace: "something-else", labels } }, { kind: "Service", metadata: { name: "tired-of-waiting-for-you", namespace: "kinda-kinks", labels } }, { kind: "Service", metadata: { name: "kubernetes", namespace: "default" } }, - { kind: "ServiceAccount", metadata: { name: "tired-of-waiting-for-you", namespace: "kinda-kinks", labels } }, - { kind: "ServiceAccount", metadata: { name: "have-you-seen-her-face", namespace: "younger-than-yesterday" } }, - { kind: "ClusterRole", metadata: { name: "the-kinks-are-the-village-green-preservation-society", labels } }, + { + kind: "ServiceAccount", + metadata: { name: "tired-of-waiting-for-you", namespace: "kinda-kinks", labels }, + }, + { + kind: "ServiceAccount", + metadata: { name: "have-you-seen-her-face", namespace: "younger-than-yesterday" }, + }, + { + kind: "ClusterRole", + metadata: { name: "the-kinks-are-the-village-green-preservation-society", labels }, + }, ]; const labelSelector: k8s.V1LabelSelector = { matchExpressions: [ { key: "rhythmGuitar", operator: "Exists" }, { key: "label", operator: "In", values: ["Pye", "Reprise", "RCA", "Arista"] }, { key: "bassoon", operator: "DoesNotExist" }, - { key: "leadVocals", operator: "NotIn", values: ["Mick Avory", "John 'Nobby' Dalton", "John Gosling"] }, + { + key: "leadVocals", + operator: "NotIn", + values: ["Mick Avory", "John 'Nobby' Dalton", "John Gosling"], + }, ], matchLabels: { artist: "The Kinks", @@ -1172,21 +1183,29 @@ describe("pack/k8s/kubernetes/fetch", () => { ]; const o = selectKubernetesResources(r, s); const e = [ - { apiVersion: "v1", kind: "Secret", metadata: { name: "you-really-got-me", namespace: "kinks", labels } }, + { + apiVersion: "v1", + kind: "Secret", + metadata: { name: "you-really-got-me", namespace: "kinks", labels }, + }, { kind: "DaemonSet", metadata: { name: "sunny-afternoon", namespace: "face2face", labels } }, { kind: "DaemonSet", metadata: { name: "sunny-afternoon", namespace: "face-to-face", labels } }, { kind: "Service", metadata: { name: "tired-of-waiting-for-you", namespace: "kinda-kinks", labels } }, { kind: "Service", metadata: { name: "kubernetes", namespace: "default" } }, - { kind: "ServiceAccount", metadata: { name: "tired-of-waiting-for-you", namespace: "kinda-kinks", labels } }, - { kind: "ClusterRole", metadata: { name: "the-kinks-are-the-village-green-preservation-society", labels } }, + { + kind: "ServiceAccount", + metadata: { name: "tired-of-waiting-for-you", namespace: "kinda-kinks", labels }, + }, + { + kind: "ClusterRole", + metadata: { name: "the-kinks-are-the-village-green-preservation-society", labels }, + }, ]; assert.deepStrictEqual(o, e); }); - }); describe("kubernetesResourceIdentity", () => { - it("should return all unique objects", () => { const o = [ { apiVersion: "v1", kind: "Secret", metadata: { name: "you-really-got-me", namespace: "kinks" } }, @@ -1215,10 +1234,26 @@ describe("pack/k8s/kubernetes/fetch", () => { it("should filter out duplicates", () => { const o = [ { kind: "Secret", metadata: { name: "you-really-got-me", namespace: "kinks" } }, - { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "waterloo-sunset", namespace: "something-else" } }, - { apiVersion: "extensions/v1beta1", kind: "Deployment", metadata: { name: "waterloo-sunset", namespace: "something-else" } }, - { apiVersion: "extensions/v1beta1", kind: "DaemonSet", metadata: { name: "sunny-afternoon", namespace: "face2face" } }, - { apiVersion: "apps/v1", kind: "DaemonSet", metadata: { name: "sunny-afternoon", namespace: "face2face" } }, + { + apiVersion: "apps/v1", + kind: "Deployment", + metadata: { name: "waterloo-sunset", namespace: "something-else" }, + }, + { + apiVersion: "extensions/v1beta1", + kind: "Deployment", + metadata: { name: "waterloo-sunset", namespace: "something-else" }, + }, + { + apiVersion: "extensions/v1beta1", + kind: "DaemonSet", + metadata: { name: "sunny-afternoon", namespace: "face2face" }, + }, + { + apiVersion: "apps/v1", + kind: "DaemonSet", + metadata: { name: "sunny-afternoon", namespace: "face2face" }, + }, { kind: "Service", metadata: { name: "tired-of-waiting-for-you", namespace: "kinda-kinks" } }, { kind: "ServiceAccount", metadata: { name: "tired-of-waiting-for-you", namespace: "kinda-kinks" } }, { kind: "ClusterRole", metadata: { name: "the-kinks-are-the-village-green-preservation-society" } }, @@ -1226,19 +1261,25 @@ describe("pack/k8s/kubernetes/fetch", () => { const u = _.uniqBy(o, kubernetesResourceIdentity); const e = [ { kind: "Secret", metadata: { name: "you-really-got-me", namespace: "kinks" } }, - { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "waterloo-sunset", namespace: "something-else" } }, - { apiVersion: "extensions/v1beta1", kind: "DaemonSet", metadata: { name: "sunny-afternoon", namespace: "face2face" } }, + { + apiVersion: "apps/v1", + kind: "Deployment", + metadata: { name: "waterloo-sunset", namespace: "something-else" }, + }, + { + apiVersion: "extensions/v1beta1", + kind: "DaemonSet", + metadata: { name: "sunny-afternoon", namespace: "face2face" }, + }, { kind: "Service", metadata: { name: "tired-of-waiting-for-you", namespace: "kinda-kinks" } }, { kind: "ServiceAccount", metadata: { name: "tired-of-waiting-for-you", namespace: "kinda-kinks" } }, { kind: "ClusterRole", metadata: { name: "the-kinks-are-the-village-green-preservation-society" } }, ]; assert.deepStrictEqual(u, e); }); - }); describe("selectorMatch", () => { - it("should match when no name/label selectors", () => { const r = { apiVersion: "v1", @@ -1315,11 +1356,9 @@ describe("pack/k8s/kubernetes/fetch", () => { }; assert(selectorMatch(r, s)); }); - }); describe("kindMatch", () => { - it("should match when no kinds", () => { [[], undefined].forEach(k => { const r = { kind: "Service" }; @@ -1356,11 +1395,9 @@ describe("pack/k8s/kubernetes/fetch", () => { ]; assert(kindMatch(r, k)); }); - }); describe("filterMatch", () => { - it("should match when no filter", () => { const r = { kind: "Service" }; assert(filterMatch(r, undefined)); @@ -1389,15 +1426,13 @@ describe("pack/k8s/kubernetes/fetch", () => { const f = (s: any) => s.kind === "Secret" && s.type === "kubernetes.io/service-account-token"; assert(filterMatch(r, f)); }); - }); - describe("kubernetesFetch", function(this: Mocha.Suite): void { - + describe("kubernetesFetch", function (this: Mocha.Suite): void { this.timeout(15000); - before(async function(this: Mocha.Context): Promise { - if (!await k8sAvailable()) { + before(async function (this: Mocha.Context): Promise { + if (!(await k8sAvailable())) { this.skip(); } beforeRetry(); @@ -1410,7 +1445,5 @@ describe("pack/k8s/kubernetes/fetch", () => { const r = await kubernetesFetch(); assert(r, "kubernetesFetch did not return anything"); }); - }); - }); diff --git a/test/core/pack/k8s/kubernetes/ingress.test.ts b/test/core/pack/k8s/kubernetes/ingress.test.ts index aa589f8e2..a556405f1 100644 --- a/test/core/pack/k8s/kubernetes/ingress.test.ts +++ b/test/core/pack/k8s/kubernetes/ingress.test.ts @@ -15,10 +15,7 @@ */ import * as assert from "power-assert"; -import { - ingressTemplate, - upsertIngress, -} from "../../../../../lib/core/pack/k8s/kubernetes/ingress"; +import { ingressTemplate, upsertIngress } from "../../../../../lib/core/pack/k8s/kubernetes/ingress"; import { KubernetesApplication, KubernetesResourceRequest, @@ -26,9 +23,7 @@ import { } from "../../../../../lib/core/pack/k8s/kubernetes/request"; describe("core/pack/k8s/kubernetes/ingress", () => { - describe("ingressTemplate", () => { - it("should create a wildcard ingress spec", async () => { const r: KubernetesApplication & KubernetesSdm = { workspaceId: "KAT3BU5H", @@ -41,7 +36,7 @@ describe("core/pack/k8s/kubernetes/ingress", () => { }; const i = await ingressTemplate(r); const e = { - apiVersion: "extensions/v1beta1", + apiVersion: "networking.k8s.io/v1beta1", kind: "Ingress", metadata: { name: "cloudbusting", @@ -101,7 +96,7 @@ describe("core/pack/k8s/kubernetes/ingress", () => { }; const s = await ingressTemplate(r); const e = { - apiVersion: "extensions/v1beta1", + apiVersion: "networking.k8s.io/v1beta1", kind: "Ingress", metadata: { annotations: { @@ -139,9 +134,7 @@ describe("core/pack/k8s/kubernetes/ingress", () => { ], tls: [ { - hosts: [ - "emi.com", - ], + hosts: ["emi.com"], secretName: "emi-com", }, ], @@ -166,7 +159,7 @@ describe("core/pack/k8s/kubernetes/ingress", () => { }; const s = await ingressTemplate(r); const e = { - apiVersion: "extensions/v1beta1", + apiVersion: "networking.k8s.io/v1beta1", kind: "Ingress", metadata: { labels: { @@ -217,7 +210,7 @@ describe("core/pack/k8s/kubernetes/ingress", () => { }; const s = await ingressTemplate(r); const e = { - apiVersion: "extensions/v1beta1", + apiVersion: "networking.k8s.io/v1beta1", kind: "Ingress", metadata: { labels: { @@ -249,11 +242,9 @@ describe("core/pack/k8s/kubernetes/ingress", () => { }; assert.deepStrictEqual(s, e); }); - }); describe("upsertIngress", () => { - it("should not do anything if port is not defined", async () => { const a: KubernetesResourceRequest = { name: "brotherhood", @@ -273,7 +264,5 @@ describe("core/pack/k8s/kubernetes/ingress", () => { const i = await upsertIngress(a); assert(i === undefined); }); - }); - }); diff --git a/test/core/pack/k8s/kubernetes/resource.test.ts b/test/core/pack/k8s/kubernetes/resource.test.ts index b163a20d3..0946e2a5d 100644 --- a/test/core/pack/k8s/kubernetes/resource.test.ts +++ b/test/core/pack/k8s/kubernetes/resource.test.ts @@ -17,16 +17,10 @@ import * as k8s from "@kubernetes/client-node"; import * as assert from "power-assert"; import { KubernetesDelete } from "../../../../../lib/core/pack/k8s/kubernetes/request"; -import { - appObject, - k8sObject, - logObject, -} from "../../../../../lib/core/pack/k8s/kubernetes/resource"; +import { appObject, k8sObject, logObject } from "../../../../../lib/core/pack/k8s/kubernetes/resource"; describe("pack/k8s/kubernetes/resource", () => { - describe("appObject", () => { - it("should throw an exception if kind invalid", () => { [undefined, "", "Nothing"].forEach(k => { const a: KubernetesDelete = { @@ -83,7 +77,7 @@ describe("pack/k8s/kubernetes/resource", () => { }); }); - it("should return a v1beta1 namespaced object", () => { + it("should return a networking.k8s.io/v1beta1 namespaced object", () => { const a: KubernetesDelete = { name: "good-girl-gone-bad", ns: "rihanna", @@ -91,7 +85,7 @@ describe("pack/k8s/kubernetes/resource", () => { }; const o = appObject(a, "Ingress"); const e = { - apiVersion: "extensions/v1beta1", + apiVersion: "networking.k8s.io/v1beta1", kind: "Ingress", metadata: { labels: { @@ -173,11 +167,9 @@ describe("pack/k8s/kubernetes/resource", () => { assert.deepStrictEqual(o, e); }); }); - }); describe("k8sObject", () => { - it("should return a minimal object from a service account", () => { const d: k8s.V1ServiceAccount = { apiVersion: "v1", @@ -236,11 +228,9 @@ describe("pack/k8s/kubernetes/resource", () => { }; assert.deepStrictEqual(o, e); }); - }); describe("stringifyObject", () => { - it("should stringify a service", () => { const d: k8s.V1Service = { apiVersion: "v1", @@ -314,7 +304,5 @@ describe("pack/k8s/kubernetes/resource", () => { const e = `{"apiVersion":"v1","data":{"One":"V******************************=","Two":"U******************=","Seven":"********"},"kind":"Secret","metadata":{"labels":{"app.kubernetes.io/name":"good-girl-gone-...}`; assert(s === e); }); - }); - }); diff --git a/test/core/pack/k8s/kubernetes/spec.test.ts b/test/core/pack/k8s/kubernetes/spec.test.ts index 387f86d6e..e09a34f10 100644 --- a/test/core/pack/k8s/kubernetes/spec.test.ts +++ b/test/core/pack/k8s/kubernetes/spec.test.ts @@ -27,9 +27,7 @@ import { /* tslint:disable:max-file-line-count */ describe("pack/k8s/kubernetes/spec", () => { - describe("kubernetesSpecFileBasename", () => { - it("should create a namespace file name", () => { const o = { apiVersion: "v1", @@ -45,7 +43,7 @@ describe("pack/k8s/kubernetes/spec", () => { it("should create a simple namespaced file name", () => { [ { a: "apps/v1", k: "Deployment", p: "70" }, - { a: "extensions/v1beta1", k: "Ingress", p: "80" }, + { a: "networking.k8s.io/v1beta1", k: "Ingress", p: "80" }, { a: "rbac.authorization.k8s.io/v1", k: "Role", p: "25" }, { a: "v1", k: "Secret", p: "60" }, { a: "v1", k: "Service", p: "50" }, @@ -108,11 +106,9 @@ describe("pack/k8s/kubernetes/spec", () => { assert(s === e); }); }); - }); describe("kubernetesSpecStringify", () => { - it("should stringify a spec", async () => { const r = { apiVersion: "v1", @@ -251,11 +247,9 @@ spec: `; assert(s === e); }); - }); describe("parseKubernetesSpecs", () => { - it("should parse JSON", async () => { const c = `{ "apiVersion": "v1", @@ -277,24 +271,26 @@ spec: } `; const s = parseKubernetesSpecs(c); - const e = [{ - apiVersion: "v1", - kind: "Service", - metadata: { - name: "satisfied-mind", - namespace: "the-byrds", - }, - spec: { - ports: [ - { - port: 80, + const e = [ + { + apiVersion: "v1", + kind: "Service", + metadata: { + name: "satisfied-mind", + namespace: "the-byrds", + }, + spec: { + ports: [ + { + port: 80, + }, + ], + selector: { + "app.kubernetes.io/name": "satisfied-mind", }, - ], - selector: { - "app.kubernetes.io/name": "satisfied-mind", }, }, - }]; + ]; assert.deepStrictEqual(s, e); }); @@ -311,22 +307,24 @@ spec: terminationGracePeriodSeconds: 180 `; const s = parseKubernetesSpecs(c); - const e = [{ - apiVersion: "apps/v1", - kind: "Deployment", - metadata: { - name: "turn-turn-turn", - namespace: "the-byrds", - }, - spec: { - template: { - spec: { - serviceAccountName: "sdm-serviceaccount", - terminationGracePeriodSeconds: 180, + const e = [ + { + apiVersion: "apps/v1", + kind: "Deployment", + metadata: { + name: "turn-turn-turn", + namespace: "the-byrds", + }, + spec: { + template: { + spec: { + serviceAccountName: "sdm-serviceaccount", + terminationGracePeriodSeconds: 180, + }, }, }, }, - }]; + ]; assert.deepStrictEqual(s, e); }); @@ -426,7 +424,7 @@ metadata: name: "cert-manager-cainjector", namespace: "cert-manager", labels: { - "app": "cainjector", + app: "cainjector", "app.kubernetes.io/name": "cainjector", "app.kubernetes.io/instance": "cert-manager", "app.kubernetes.io/managed-by": "Tiller", @@ -441,7 +439,7 @@ metadata: name: "cert-manager-webhook", namespace: "cert-manager", labels: { - "app": "webhook", + app: "webhook", "app.kubernetes.io/name": "webhook", "app.kubernetes.io/instance": "cert-manager", "app.kubernetes.io/managed-by": "Tiller", @@ -456,7 +454,7 @@ metadata: name: "cert-manager", namespace: "cert-manager", labels: { - "app": "cert-manager", + app: "cert-manager", "app.kubernetes.io/name": "cert-manager", "app.kubernetes.io/instance": "cert-manager", "app.kubernetes.io/managed-by": "Tiller", @@ -467,11 +465,9 @@ metadata: ]; assert.deepStrictEqual(s, e); }); - }); describe("specSlug", () => { - it("should return a namespaced slug", () => { const s = { apiVersion: "v1", @@ -512,11 +508,9 @@ metadata: const e = "policy/v1beta1/podsecuritypolicies/mermaid"; assert(l === e); }); - }); describe("specSnippet", () => { - it("should return the full string", () => { const s = { apiVersion: "v1", @@ -568,11 +562,9 @@ metadata: const e = `{"apiVersion":"v1","kind":"Service","metadata":{"labels":{"component":"apiserver","provider":"kubernetes"},"name":"kubernetes","namespace":"default","resourceVersion":"37","selfLink":"/api/v1/name...}`; assert(l === e); }); - }); describe("specSnippet", () => { - it("should return the full string", () => { const s = `{"apiVersion":"v1","kind":"Service","metadata":{"name":"mermaid","namespace":"avenue"}}`; const l = specStringSnippet(s); @@ -587,7 +579,5 @@ metadata: /* tslint:enable:max-line-length */ assert(l === e); }); - }); - }); diff --git a/test/core/pack/k8s/sync/application.test.ts b/test/core/pack/k8s/sync/application.test.ts index 0c7ac247a..5e164b40d 100644 --- a/test/core/pack/k8s/sync/application.test.ts +++ b/test/core/pack/k8s/sync/application.test.ts @@ -31,9 +31,7 @@ import { import { k8sSpecGlob } from "../../../../../lib/core/pack/k8s/sync/diff"; describe("pack/k8s/sync/application", () => { - describe("sameObject", () => { - it("should return true for equivalent objects", () => { [ [ @@ -80,14 +78,8 @@ describe("pack/k8s/sync/application", () => { it("should return false for invalid objects", () => { [ - [ - { kind: "Service", metadata: { name: "emmylou", namespace: "harris" } }, - undefined, - ], - [ - { kind: "ClusterRole", metadata: { name: "emmylou" } }, - { kind: "ClusterRole" }, - ], + [{ kind: "Service", metadata: { name: "emmylou", namespace: "harris" } }, undefined], + [{ kind: "ClusterRole", metadata: { name: "emmylou" } }, { kind: "ClusterRole" }], [ { kind: "Service", metadata: { name: "emmylou", namespace: "harris" } }, { metadata: { name: "emmylou", namespace: "harris" } }, @@ -101,11 +93,9 @@ describe("pack/k8s/sync/application", () => { assert(!sameObject(oo[1], oo[0])); }); }); - }); describe("matchSpec", () => { - it("should find nothing", () => { const sss = [ [], @@ -269,18 +259,22 @@ describe("pack/k8s/sync/application", () => { const m = matchSpec(s, ss); assert.deepStrictEqual(m, ss[2]); }); - }); describe("syncResources", () => { - it("should create spec files", async () => { const p: GitProject = InMemoryProject.of() as any; p.isClean = async () => false; let commitMessage: string; - p.commit = async msg => { commitMessage = msg; return p; }; + p.commit = async msg => { + commitMessage = msg; + return p; + }; let pushed = false; - p.push = async msg => { pushed = true; return p; }; + p.push = async msg => { + pushed = true; + return p; + }; const a = { image: "hub.tonina.com/black-angel/como-yo:3.58", name: "tonina", @@ -341,7 +335,7 @@ describe("pack/k8s/sync/application", () => { `; assert(commitMessage === eCommitMessage); assert(pushed, "commit was not pushed"); - assert(await p.totalFileCount() === 4); + assert((await p.totalFileCount()) === 4); assert(p.fileExistsSync("70_black-angel_tonina_deployment.json")); assert(p.fileExistsSync("50_black-angel_tonina_service.json")); assert(p.fileExistsSync("20_black-angel_tonina_service-account.json")); @@ -379,9 +373,15 @@ describe("pack/k8s/sync/application", () => { const p: GitProject = InMemoryProject.of() as any; p.isClean = async () => false; let commitMessage: string; - p.commit = async msg => { commitMessage = msg; return p; }; + p.commit = async msg => { + commitMessage = msg; + return p; + }; let pushed = false; - p.push = async msg => { pushed = true; return p; }; + p.push = async msg => { + pushed = true; + return p; + }; const a = { image: "hub.tonina.com/black-angel/como-yo:3.58", name: "tonina", @@ -420,7 +420,7 @@ describe("pack/k8s/sync/application", () => { `; assert(commitMessage === eCommitMessage); assert(pushed, "commit was not pushed"); - assert(await p.totalFileCount() === 2); + assert((await p.totalFileCount()) === 2); assert(p.fileExistsSync("70_black-angel_tonina_deployment.yaml")); assert(p.fileExistsSync("50_black-angel_tonina_service.yaml")); const d = await (await p.getFile("70_black-angel_tonina_deployment.yaml")).getContent(); @@ -463,9 +463,15 @@ metadata: ) as any; p.isClean = async () => false; let commitMessage: string; - p.commit = async msg => { commitMessage = msg; return p; }; + p.commit = async msg => { + commitMessage = msg; + return p; + }; let pushed = false; - p.push = async msg => { pushed = true; return p; }; + p.push = async msg => { + pushed = true; + return p; + }; const a = { image: "hub.tonina.com/black-angel/como-yo:3.5.8-20180406", name: "tonina", @@ -493,7 +499,7 @@ metadata: }, }, { - apiVersion: "extensions/v1beta1", + apiVersion: "networking.k8s.io/v1beta1", kind: "Ingress", metadata: { name: "tonina", @@ -541,7 +547,7 @@ metadata: `; assert(commitMessage === eCommitMessage); assert(pushed, "commit was not pushed"); - assert(await p.totalFileCount() === 6); + assert((await p.totalFileCount()) === 6); assert(p.fileExistsSync("70_black-angel_tonina_deployment.json")); assert(p.fileExistsSync("50_black-angel_tonina_service.json")); assert(p.fileExistsSync("80_black-angel_tonina_ingress.json")); @@ -618,9 +624,15 @@ metadata: ) as any; p.isClean = async () => false; let commitMessage: string; - p.commit = async msg => { commitMessage = msg; return p; }; + p.commit = async msg => { + commitMessage = msg; + return p; + }; let pushed = false; - p.push = async msg => { pushed = true; return p; }; + p.push = async msg => { + pushed = true; + return p; + }; const a = { name: "tonina", ns: "black-angel", @@ -647,7 +659,7 @@ metadata: }, }, { - apiVersion: "extensions/v1beta1", + apiVersion: "networking.k8s.io/v1beta1", kind: "Ingress", metadata: { name: "tonina", @@ -680,7 +692,7 @@ metadata: `; assert(commitMessage === eCommitMessage); assert(pushed, "commit was not pushed"); - assert(await p.totalFileCount() === 1); + assert((await p.totalFileCount()) === 1); assert(!p.fileExistsSync("black-angel~tonina~deployment.json")); assert(p.fileExistsSync("black-angel-tonina-service.json")); assert(!p.fileExistsSync("black-angel-tonina-ingress.json")); @@ -689,7 +701,5 @@ metadata: const svc = await p.getFile("black-angel-tonina-service.json").then(f => f.getContent()); assert(svc === "{}\n"); }); - }); - });