Skip to content
This repository has been archived by the owner on Jun 22, 2020. It is now read-only.

Commit

Permalink
Update Kubernetes client, workaround bluebird
Browse files Browse the repository at this point in the history
@kubernetes/client-node@0.8.0 exposes bluebird promises in its API,
causing a lot of breaking changes.  See
kubernetes-client/javascript#198 for more
information.  Implement various fixes to workaround the issue.

This commit bites the bullet because exec-based auth is broken in
earlier versions.  The order of arguments for delete methods also
changed, so update that.
  • Loading branch information
David Dooling committed Jan 31, 2019
1 parent 8b29411 commit d36355d
Show file tree
Hide file tree
Showing 10 changed files with 77 additions and 58 deletions.
19 changes: 10 additions & 9 deletions lib/kubernetes/clients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import { logger } from "@atomist/automation-client";
import * as k8s from "@kubernetes/client-node";
import Bluebird = require("bluebird"); // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/11027
import * as stringify from "json-stringify-safe";
import { errMsg } from "../support/error";

Expand All @@ -42,40 +43,40 @@ async function patchWithHeaders(api: any, patcher: any, args: any[]) {

class Core_v1Api_Patch extends k8s.Core_v1Api {
patchNamespacedSecret(...args: any[]) {
return patchWithHeaders(this, super.patchNamespacedSecret, args);
return Bluebird.resolve(patchWithHeaders(this, super.patchNamespacedSecret, args));
}
patchNamespacedService(...args: any[]) {
return patchWithHeaders(this, super.patchNamespacedService, args);
return Bluebird.resolve(patchWithHeaders(this, super.patchNamespacedService, args));
}
patchNamespacedServiceAccount(...args: any[]) {
return patchWithHeaders(this, super.patchNamespacedServiceAccount, args);
return Bluebird.resolve(patchWithHeaders(this, super.patchNamespacedServiceAccount, args));
}
}

class Apps_v1Api_Patch extends k8s.Apps_v1Api {
patchNamespacedDeployment(...args: any[]) {
return patchWithHeaders(this, super.patchNamespacedDeployment, args);
return Bluebird.resolve(patchWithHeaders(this, super.patchNamespacedDeployment, args));
}
}

class Extensions_v1beta1Api_Patch extends k8s.Extensions_v1beta1Api {
patchNamespacedIngress(...args: any[]) {
return patchWithHeaders(this, super.patchNamespacedIngress, args);
return Bluebird.resolve(patchWithHeaders(this, super.patchNamespacedIngress, args));
}
}

class RbacAuthorization_v1Api_Patch extends k8s.RbacAuthorization_v1Api {
patchClusterRole(...args: any[]) {
return patchWithHeaders(this, super.patchClusterRole, args);
return Bluebird.resolve(patchWithHeaders(this, super.patchClusterRole, args));
}
patchNamespacedRole(...args: any[]) {
return patchWithHeaders(this, super.patchNamespacedRole, args);
return Bluebird.resolve(patchWithHeaders(this, super.patchNamespacedRole, args));
}
patchClusterRoleBinding(...args: any[]) {
return patchWithHeaders(this, super.patchClusterRoleBinding, args);
return Bluebird.resolve(patchWithHeaders(this, super.patchClusterRoleBinding, args));
}
patchNamespacedRoleBinding(...args: any[]) {
return patchWithHeaders(this, super.patchNamespacedRoleBinding, args);
return Bluebird.resolve(patchWithHeaders(this, super.patchNamespacedRoleBinding, args));
}
}
/* tslint:enable */
Expand Down
6 changes: 3 additions & 3 deletions lib/kubernetes/deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export async function upsertDeployment(req: KubernetesResourceRequest): Promise<
const slug = appName(req);
const spec = await deploymentTemplate(req);
try {
await req.clients.apps.readNamespacedDeployment(req.name, req.ns);
await Promise.resolve(req.clients.apps.readNamespacedDeployment(req.name, req.ns));
} catch (e) {
logger.debug(`Failed to read deployment ${slug}, creating: ${errMsg(e)}`);
logger.debug(`Creating deployment ${slug} using '${stringify(spec)}'`);
Expand All @@ -75,13 +75,13 @@ export async function upsertDeployment(req: KubernetesResourceRequest): Promise<
export async function deleteDeployment(req: KubernetesDeleteResourceRequest): Promise<void> {
const slug = appName(req);
try {
await req.clients.apps.readNamespacedDeployment(req.name, req.ns);
await Promise.resolve(req.clients.apps.readNamespacedDeployment(req.name, req.ns));
} catch (e) {
logger.debug(`Deployment ${slug} does not exist: ${errMsg(e)}`);
return;
}
const body: k8s.V1DeleteOptions = { propagationPolicy: "Background" } as any;
await logRetry(() => req.clients.apps.deleteNamespacedDeployment(req.name, req.ns, body),
await logRetry(() => req.clients.apps.deleteNamespacedDeployment(req.name, req.ns, "", body),
`delete deployment ${slug}`);
return;
}
Expand Down
7 changes: 3 additions & 4 deletions lib/kubernetes/ingress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export async function upsertIngress(req: KubernetesResourceRequest): Promise<Ups
}
const spec = await ingressTemplate(req);
try {
await req.clients.ext.readNamespacedIngress(req.name, req.ns);
await Promise.resolve(req.clients.ext.readNamespacedIngress(req.name, req.ns));
} catch (e) {
logger.debug(`Failed to read ingress ${slug}, creating: ${errMsg(e)}`);
logger.debug(`Creating ingress ${slug} using '${stringify(spec)}'`);
Expand All @@ -72,13 +72,12 @@ export async function upsertIngress(req: KubernetesResourceRequest): Promise<Ups
export async function deleteIngress(req: KubernetesDeleteResourceRequest): Promise<void> {
const slug = appName(req);
try {
await req.clients.ext.readNamespacedIngress(req.name, req.ns);
await Promise.resolve(req.clients.ext.readNamespacedIngress(req.name, req.ns));
} catch (e) {
logger.debug(`Ingress ${slug} does not exist: ${errMsg(e)}`);
return;
}
const body: k8s.V1DeleteOptions = {} as any;
await logRetry(() => req.clients.ext.deleteNamespacedIngress(req.name, req.ns, body), `delete ingress ${slug}`);
await logRetry(() => req.clients.ext.deleteNamespacedIngress(req.name, req.ns), `delete ingress ${slug}`);
return;
}

Expand Down
2 changes: 1 addition & 1 deletion lib/kubernetes/namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export interface UpsertNamespaceResponse {
*/
export async function upsertNamespace(req: KubernetesResourceRequest): Promise<UpsertNamespaceResponse> {
try {
const resp = await req.clients.core.readNamespace(req.ns);
const resp = await Promise.resolve(req.clients.core.readNamespace(req.ns));
logger.debug(`Namespace ${req.ns} exists`);
return resp;
} catch (e) {
Expand Down
23 changes: 11 additions & 12 deletions lib/kubernetes/rbac.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,19 +88,18 @@ export async function upsertRbac(req: KubernetesResourceRequest): Promise<Upsert
*/
export async function deleteRbac(req: KubernetesDeleteResourceRequest): Promise<void> {
const slug = appName(req);
const body: k8s.V1DeleteOptions = {} as any;
const errs: Error[] = [];

let roleBindingExists = false;
try {
await req.clients.rbac.readNamespacedRoleBinding(req.name, req.ns);
await Promise.resolve(req.clients.rbac.readNamespacedRoleBinding(req.name, req.ns));
roleBindingExists = true;
} catch (e) {
logger.debug(`Role binding ${slug} does not exist: ${errMsg(e)}`);
}
if (roleBindingExists) {
try {
await logRetry(() => req.clients.rbac.deleteNamespacedRoleBinding(req.name, req.ns, body), `delete role binding ${slug}`);
await logRetry(() => req.clients.rbac.deleteNamespacedRoleBinding(req.name, req.ns), `delete role binding ${slug}`);
} catch (e) {
e.message = `Failed to delete role binding ${slug}: ${errMsg(e)}`;
errs.push(e);
Expand All @@ -109,14 +108,14 @@ export async function deleteRbac(req: KubernetesDeleteResourceRequest): Promise<

let serviceAccountExists = false;
try {
await req.clients.core.readNamespacedServiceAccount(req.name, req.ns);
await Promise.resolve(req.clients.core.readNamespacedServiceAccount(req.name, req.ns));
serviceAccountExists = true;
} catch (e) {
logger.debug(`Service account ${slug} does not exist: ${errMsg(e)}`);
}
if (serviceAccountExists) {
try {
await logRetry(() => req.clients.core.deleteNamespacedServiceAccount(req.name, req.ns, body), `delete service account ${slug}`);
await logRetry(() => req.clients.core.deleteNamespacedServiceAccount(req.name, req.ns), `delete service account ${slug}`);
} catch (e) {
e.message = `Failed to delete service account ${slug}: ${errMsg(e)}`;
errs.push(e);
Expand All @@ -125,14 +124,14 @@ export async function deleteRbac(req: KubernetesDeleteResourceRequest): Promise<

let roleExists = false;
try {
await req.clients.rbac.readNamespacedRole(req.name, req.ns);
await Promise.resolve(req.clients.rbac.readNamespacedRole(req.name, req.ns));
roleExists = true;
} catch (e) {
logger.debug(`Role ${slug} does not exist: ${errMsg(e)}`);
}
if (roleExists) {
try {
await logRetry(() => req.clients.rbac.deleteNamespacedRole(req.name, req.ns, body), `delete role ${slug}`);
await logRetry(() => req.clients.rbac.deleteNamespacedRole(req.name, req.ns), `delete role ${slug}`);
} catch (e) {
e.message = `Failed to delete role ${slug}: ${errMsg(e)}`;
errs.push(e);
Expand All @@ -158,7 +157,7 @@ async function upsertServiceAccount(req: KubernetesResourceRequest): Promise<Ups
const slug = appName(req);
const spec = await serviceAccountTemplate(req);
try {
await req.clients.core.readNamespacedServiceAccount(req.name, req.ns);
await Promise.resolve(req.clients.core.readNamespacedServiceAccount(req.name, req.ns));
} catch (e) {
logger.debug(`Failed to read service account ${slug}, creating: ${errMsg(e)}`);
return logRetry(() => req.clients.core.createNamespacedServiceAccount(req.ns, spec),
Expand All @@ -180,7 +179,7 @@ async function upsertRole(req: KubernetesResourceRequest): Promise<UpsertRoleRes
if (req.roleSpec.kind === "ClusterRole") {
const spec = await clusterRoleTemplate(req);
try {
await req.clients.rbac.readClusterRole(req.name);
await Promise.resolve(req.clients.rbac.readClusterRole(req.name));
} catch (e) {
logger.debug(`Failed to read cluster role ${slug}, creating: ${errMsg(e)}`);
logger.debug(`Creating cluster role ${slug} using '${stringify(spec)}'`);
Expand All @@ -191,7 +190,7 @@ async function upsertRole(req: KubernetesResourceRequest): Promise<UpsertRoleRes
} else {
const spec = await roleTemplate(req);
try {
await req.clients.rbac.readNamespacedRole(req.name, req.ns);
await Promise.resolve(req.clients.rbac.readNamespacedRole(req.name, req.ns));
} catch (e) {
logger.debug(`Failed to read role ${slug}, creating: ${errMsg(e)}`);
return logRetry(() => req.clients.rbac.createNamespacedRole(req.ns, spec), `create role ${slug}`);
Expand All @@ -212,7 +211,7 @@ async function upsertRoleBinding(req: KubernetesResourceRequest): Promise<Upsert
if (req.roleSpec.kind === "ClusterRole") {
const spec = await clusterRoleBindingTemplate(req);
try {
await req.clients.rbac.readClusterRoleBinding(req.name);
await Promise.resolve(req.clients.rbac.readClusterRoleBinding(req.name));
} catch (e) {
logger.debug(`Failed to read cluster role binding ${slug}, creating: ${errMsg(e)}`);
return logRetry(() => req.clients.rbac.createClusterRoleBinding(spec),
Expand All @@ -224,7 +223,7 @@ async function upsertRoleBinding(req: KubernetesResourceRequest): Promise<Upsert
} else {
const spec = await roleBindingTemplate(req);
try {
await req.clients.rbac.readNamespacedRoleBinding(req.name, req.ns);
await Promise.resolve(req.clients.rbac.readNamespacedRoleBinding(req.name, req.ns));
} catch (e) {
logger.debug(`Failed to read role binding ${slug}, creating: ${errMsg(e)}`);
return logRetry(() => req.clients.rbac.createNamespacedRoleBinding(req.ns, spec),
Expand Down
7 changes: 3 additions & 4 deletions lib/kubernetes/secret.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export async function upsertSecrets(req: KubernetesResourceRequest): Promise<Ups
const secretName = `${req.ns}/${secret.metadata.name}`;
const spec = await secretTemplate(req, secret);
try {
await req.clients.core.readNamespacedSecret(secret.metadata.name, req.ns);
await Promise.resolve(req.clients.core.readNamespacedSecret(secret.metadata.name, req.ns));
} catch (e) {
logger.debug(`Failed to read secret ${secretName}, creating: ${errMsg(e)}`);
return logRetry(() => req.clients.core.createNamespacedSecret(req.ns, spec),
Expand All @@ -81,17 +81,16 @@ export async function deleteSecrets(req: KubernetesDeleteResourceRequest): Promi
const slug = appName(req);
let secrets: k8s.V1SecretList;
try {
const listResp = await req.clients.core.listNamespacedSecret(req.ns);
const listResp = await Promise.resolve(req.clients.core.listNamespacedSecret(req.ns));
secrets = listResp.body;
} catch (e) {
logger.debug(`Failed to list secrets in namespace ${req.ns}, not deleting secrets for ${slug}: ${errMsg(e)}`);
return;
}
const appSecrets = applicationSecrets(req, secrets.items);
const body: k8s.V1DeleteOptions = {} as any;
for (const secret of appSecrets) {
const secretName = `${req.ns}/${secret.metadata.name}`;
await logRetry(() => req.clients.core.deleteNamespacedSecret(secret.metadata.name, req.ns, body),
await logRetry(() => req.clients.core.deleteNamespacedSecret(secret.metadata.name, req.ns),
`delete secret ${secretName}`);
}
return;
Expand Down
7 changes: 3 additions & 4 deletions lib/kubernetes/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export async function upsertService(req: KubernetesResourceRequest): Promise<Ups
}
const spec = await serviceTemplate(req);
try {
await req.clients.core.readNamespacedService(req.name, req.ns);
await Promise.resolve(req.clients.core.readNamespacedService(req.name, req.ns));
} catch (e) {
logger.debug(`Failed to read service ${slug}, creating: ${errMsg(e)}`);
logger.debug(`Creating service ${slug} using '${stringify(spec)}'`);
Expand All @@ -76,13 +76,12 @@ export async function upsertService(req: KubernetesResourceRequest): Promise<Ups
export async function deleteService(req: KubernetesDeleteResourceRequest): Promise<void> {
const slug = appName(req);
try {
await req.clients.core.readNamespacedService(req.name, req.ns);
await Promise.resolve(req.clients.core.readNamespacedService(req.name, req.ns));
} catch (e) {
logger.debug(`Service ${slug} does not exist: ${errMsg(e)}`);
return;
}
const body: k8s.V1DeleteOptions = {} as any;
await logRetry(() => req.clients.core.deleteNamespacedService(req.name, req.ns, body), `delete service ${slug}`);
await logRetry(() => req.clients.core.deleteNamespacedService(req.name, req.ns), `delete service ${slug}`);
return;
}

Expand Down
3 changes: 2 additions & 1 deletion lib/support/retry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import { logger } from "@atomist/automation-client";
import Bluebird = require("bluebird"); // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/11027
import * as pRetry from "p-retry";
import { errMsg } from "./error";

Expand All @@ -29,7 +30,7 @@ const defaultRetryOptions: pRetry.Options = {
/**
* Add logging to promise-based retry.
*/
export async function logRetry<T>(f: () => Promise<T>, desc: string, options: pRetry.Options = defaultRetryOptions): Promise<T> {
export async function logRetry<T>(f: () => Bluebird<T>, desc: string, options: pRetry.Options = defaultRetryOptions): Promise<T> {
const opts: pRetry.Options = {
onFailedAttempt: e => {
logger.debug(`Error in ${desc} attempt ${e.attemptNumber}: ${errMsg(e)}`);
Expand Down

0 comments on commit d36355d

Please sign in to comment.