Skip to content

Instantly share code, notes, and snippets.

@brandonkal
Last active December 12, 2019 22:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brandonkal/af95cc41a7548a0e323fac90cbe2df23 to your computer and use it in GitHub Desktop.
Save brandonkal/af95cc41a7548a0e323fac90cbe2df23 to your computer and use it in GitHub Desktop.
Pulumi Custom Resource Validation
// Replacing line 172 to throw a plain Error
Diagnostics:
pulumi:pulumi:Stack (crd-crd):
Error: No events found for SealedSecret ss-test @ https://127.0.0.1:6443/api/v1/namespaces/default/events?fieldSelector=involvedObject.name%3Dss-test%2CinvolvedObject.namespace%3Ddefault%2CinvolvedObject.kind%3DSealedSecret: {"kind":"EventList","apiVersion":"v1","metadata":{"selfLink":"/api/v1/namespaces/default/events","resourceVersion":"708310"},"items":[]}
at SealedSecret.<anonymous> (/home/brandon/pulumi-project/lib/sealedsecret.ts:175:15)
at Generator.throw (<anonymous>)
at rejected (/home/brandon/pulumi-project/lib/sealedsecret.ts:6:65)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
Error: No events found for SealedSecret ss-test @ https://127.0.0.1:6443/api/v1/namespaces/default/events?fieldSelector=involvedObject.name%3Dss-test%2CinvolvedObject.namespace%3Ddefault%2CinvolvedObject.kind%3DSealedSecret: {"kind":"EventList","apiVersion":"v1","metadata":{"selfLink":"/api/v1/namespaces/default/events","resourceVersion":"708310"},"items":[]}
at SealedSecret.<anonymous> (/home/brandon/pulumi-project/lib/sealedsecret.ts:175:15)
at Generator.throw (<anonymous>)
at rejected (/home/brandon/pulumi-project/lib/sealedsecret.ts:6:65)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
error: Running program '/home/brandon/pulumi-project/08-crd' failed with an unhandled exception:
Error: No events found for SealedSecret ss-test @ https://127.0.0.1:6443/api/v1/namespaces/default/events?fieldSelector=involvedObject.name%3Dss-test%2CinvolvedObject.namespace%3Ddefault%2CinvolvedObject.kind%3DSealedSecret: {"kind":"EventList","apiVersion":"v1","metadata":{"selfLink":"/api/v1/namespaces/default/events","resourceVersion":"708310"},"items":[]}
at SealedSecret.<anonymous> (/home/brandon/pulumi-project/lib/sealedsecret.ts:175:15)
at Generator.throw (<anonymous>)
at rejected (/home/brandon/pulumi-project/lib/sealedsecret.ts:6:65)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
// See revision 2 of this gist for a similar duplicate error
Diagnostics:
pulumi:pulumi:Stack (crd-crd):
Error: No events found for SealedSecret ss-test @ https://127.0.0.1:6443/api/v1/namespaces/default/events?fieldSelector=involvedObject.name%3Dss-test%2CinvolvedObject.namespace%3Ddefault%2CinvolvedObject.kind%3DSealedSecret: {"kind":"EventList","apiVersion":"v1","metadata":{"selfLink":"/api/v1/namespaces/default/events","resourceVersion":"706082"},"items":[]}
at SealedSecret.<anonymous> (/home/brandon/pulumi-project/lib/sealedsecret.ts:173:15)
at Generator.throw (<anonymous>)
at rejected (/home/brandon/pulumi-project/lib/sealedsecret.ts:6:65)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
Error: No events found for SealedSecret ss-test @ https://127.0.0.1:6443/api/v1/namespaces/default/events?fieldSelector=involvedObject.name%3Dss-test%2CinvolvedObject.namespace%3Ddefault%2CinvolvedObject.kind%3DSealedSecret: {"kind":"EventList","apiVersion":"v1","metadata":{"selfLink":"/api/v1/namespaces/default/events","resourceVersion":"706082"},"items":[]}
at SealedSecret.<anonymous> (/home/brandon/pulumi-project/lib/sealedsecret.ts:173:15)
at Generator.throw (<anonymous>)
at rejected (/home/brandon/pulumi-project/lib/sealedsecret.ts:6:65)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
sealedsecret (ss-test):
error: No events found for SealedSecret ss-test @ https://127.0.0.1:6443/api/v1/namespaces/default/events?fieldSelector=involvedObject.name%3Dss-test%2CinvolvedObject.namespace%3Ddefault%2CinvolvedObject.kind%3DSealedSecret: {"kind":"EventList","apiVersion":"v1","metadata":{"selfLink":"/api/v1/namespaces/default/events","resourceVersion":"706082"},"items":[]}
import * as k8s from '@pulumi/kubernetes'
import { types } from '@pulumi/kubernetesx'
import * as pulumi from '@pulumi/pulumi'
import * as k8sClient from '@kubernetes/client-node'
import * as request from 'request'
import { promisify } from 'util'
import * as jsyaml from 'js-yaml'
import * as fs from 'fs'
const rget = promisify(request.get)
type SealedSecretArgs = { file: string } | { yaml: string }
interface Meta {
name: string
namespace: string
[key: string]: string
}
interface Spec {
template: {
metadata: Meta
}
encryptedData: Record<string, string>
}
export interface Sealed {
kind: 'SealedSecret'
apiVersion: 'bitnami.com/v1alpha1'
metadata: Meta
spec: Spec
}
function isSealedSecret(obj: any): obj is Sealed {
if (
obj != null &&
typeof obj === 'object' &&
obj.kind === 'SealedSecret' &&
obj.apiVersion === 'bitnami.com/v1alpha1' &&
obj.metadata &&
typeof obj.metadata.name === 'string' &&
typeof obj.metadata.namespace === 'string' &&
obj.spec &&
obj.spec.template &&
obj.spec.template.metadata &&
typeof obj.spec.template.metadata.name === 'string' &&
typeof obj.spec.template.metadata.namespace === 'string' &&
obj.spec.encryptedData
) {
return true
}
return false
}
/**
* SealedSecret takes a Bitnami SealedSecret and applies it to the kubernetes cluster. It uses the Kubernetes API to look at events related to the SealedSecret to verify that the secret could be unsealed.
* It also provides kx-like methods for referencing the unsealed secret including `mount()` and `asEnvValue()`.
* It assumes the default kubeconfig cluster.
*/
export class SealedSecret extends pulumi.ComponentResource {
public readonly metadata: pulumi.Output<Meta>
public readonly spec: pulumi.Output<Spec>
public readonly unsealed: pulumi.Output<boolean>
mount(
destPath: pulumi.Input<string>,
srcPath?: pulumi.Input<string>
): pulumi.Output<types.VolumeMount> {
return pulumi.output({
volume: {
name: this.metadata.name,
secret: {
secretName: this.metadata.name,
},
},
destPath: destPath,
srcPath: srcPath,
})
}
asEnvValue(
key: pulumi.Input<string>
): pulumi.Output<k8s.types.input.core.v1.EnvVarSource> {
return pulumi.output({
secretKeyRef: {
name: this.metadata.name,
key: key,
},
})
}
constructor(
name: string,
args: SealedSecretArgs,
opts: pulumi.CustomResourceOptions = {}
) {
super('sealedsecret', name, undefined, opts)
let content: string
if ('file' in args) {
content = fs.readFileSync(args.file).toString()
} else {
content = args.yaml
}
const obj = jsyaml.safeLoad(content)
if (!isSealedSecret(obj)) {
throw new pulumi.ResourceError(
`Expected a SealedSecret but got: ${JSON.stringify(obj)}`,
this,
true
)
}
this.metadata = pulumi.output(obj.metadata)
this.spec = pulumi.output(obj.spec)
opts.parent = this
const ss = new k8s.apiextensions.CustomResource(name, obj, opts)
const secName = obj.metadata.name
function awaitUnseal(input: any): Promise<boolean> {
return new Promise(async (resolve, reject) => {
if (!pulumi.runtime.isDryRun()) {
const kc = new k8sClient.KubeConfig()
kc.loadFromDefault()
const opts = {} as any
kc.applyToRequest(opts)
const cluster = kc.getCurrentCluster()
if (cluster == null) {
reject(
new pulumi.RunError(
'Unable to configure kubernetes client for sealedsecret'
)
)
return
}
const url = `${cluster.server}/api/v1/namespaces/${
obj.metadata.namespace
}/events?fieldSelector=${encodeURIComponent(
`involvedObject.name=${secName},involvedObject.namespace=${obj.metadata.namespace},involvedObject.kind=SealedSecret`
)}`
const result = await rget({ ...opts, url: url })
const body = await JSON.parse(result.body)
if (body.items && body.items.length) {
const event = body.items.pop()
if (event.reason !== 'Unsealed') {
reject(new pulumi.ResourceError(event.message, this, true))
return
}
resolve(event)
return
} else {
reject(
new Error(
`No events found for SealedSecret ${secName} @ ${url}: ${JSON.stringify(
body
)}`
)
)
}
} else {
resolve(input)
return
}
})
}
this.unsealed = pulumi.output(false)
this.unsealed = pulumi.output(ss).apply(async (res) => {
try {
await awaitUnseal(res)
return true
} catch (e) {
throw new pulumi.ResourceError(e.message, this, true)
}
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment