import { logger , ProjectFile , projectUtils , } from "@atomist/automation-client" ; import { ApplyFingerprint , Aspect , DefaultTargetDiffHandler , DiffSummaryFingerprint , ExtractFingerprint , FP , sha256 , } from "@atomist/sdm-pack-fingerprint" ; import { bold } from "@atomist/slack-messages" ; import * as yaml from "js-yaml" ; import * as stringify from "json-stable-stringify" ; const K8sSpecsType = "k8s-specs" ; export interface K8sObject { apiVersion : string ; kind : string ; metadata : { name : string ; namespace ? : string ; labels ? : Record < string , string > ; } ; } param spec return export function createK8sSpecFingerprint ( spec ) { if ( spec . metadata && spec . metadata . labels && spec . metadata . labels [ "app.kubernetes.io/managed-by" ] ) { delete spec . metadata . labels [ "app.kubernetes.io/managed-by" ] ; } const s = spec ; if ( s . spec && s . spec . template && s . spec . template . metadata && s . spec . template . metadata . labels && s . spec . template . metadata . labels [ "app.kubernetes.io/managed-by" ] ) { delete s . spec . template . metadata . labels [ "app.kubernetes.io/managed-by" ] ; } return { type : K8sSpecsType , name : k8sSpecsFingerprintName ( spec ) , abbreviation : "k8sspecs" , version : "0.0.1" , data : spec , sha : sha256 ( stringify ( spec ) ) , } ; } export function k8sSpecsFingerprintName ( spec ) { if ( ! isK8sSpec ( spec ) ) { throw new Error ( `The provided Kubernetes spec is not valid: ${ stringify ( spec ) }` ) ; } const ns = ( spec . metadata . namespace ) ? `${ spec . metadata . namespace }::` : "" ; return `${ spec . kind }::${ ns }${ spec . metadata . name }` ; } param fingerprintName return export function k8sSpecsFingerprintDisplayName ( fingerprintName ) { return fingerprintName . replace ( /::/g , "/" ) ; } /**
 * Read file, parse, and verify it looks like a Kubernetes spec.  If so,
 * it returns the parse spec object.  If not, it returns `undefined`.
 */ export async function parseK8sSpecFile ( f ) { const specString = await f . getContent ( ) ; try { const spec = yaml . safeLoad ( specString ) ; if ( isK8sSpec ( spec ) ) { return spec ; } } catch ( e ) { logger . warn ( `Failed to parse '${ f . name }', skipping: ${ e . message }` ) ; } return undefined ; } /**
 * Test if the provided object looks like a valid Kubernetes resource object.
 */ export function isK8sSpec ( o ) { return o . apiVersion && o . kind && o . metadata && o . metadata . name ; } const k8sSpecGlob = "*.@(json|yaml|yml)" ; export const createK8sSpecsFingerprints = async ( p ) => { const fingerprints = [ ] ; await projectUtils . doWithFiles ( p , k8sSpecGlob , async ( f ) => { const spec = await parseK8sSpecFile ( f ) ; if ( spec ) { fingerprints . push ( createK8sSpecFingerprint ( spec ) ) ; } } ) ; return fingerprints ; } ; export const applyK8sSpecsFingerprint = async ( p , papi ) => { const fp = papi . parameters . fp ; const specFiles = await projectUtils . toPromise ( p . streamFiles ( k8sSpecGlob ) ) ; for ( const specFile of specFiles ) { const spec = await parseK8sSpecFile ( specFile ) ; if ( spec && k8sSpecsFingerprintName ( spec ) === fp . name ) { const specString = ( /\.ya?ml$/ . test ( specFile . name ) ) ? yaml . safeDump ( fp . data ) : stringify ( fp . data ) ; await specFile . setContent ( specString ) ; } } return p ; } ; export const diffK8sSpecsFingerprints = ( diff , target ) => { const resourceName = k8sSpecsFingerprintDisplayName ( k8sSpecsFingerprintName ( diff . from . data ) ) ; const repo = `${ diff . owner }/${ diff . repo }/${ diff . branch }` ; return { title : "New Kubernetes Spec Policy" , description : `Policy version for Kubernetes spec ${ bold ( resourceName ) } differs from that in ${ bold ( repo ) }.` , } ; } ; /**
 * Aspect emitting 0 or more Kubernetes resource spec fingerprints.
 * Each fingerprint represents a single resource spec.  Across
 * multiple repositories, the fingerprints can be compared to check
 * for drift in resources that provide core functionality like
 * external-dns and nginx-ingress.  If a repo does not contain any
 * JSON or YAML files in the root of the project that look like
 * Kubernetes resource specs, no fingerprints are emitted.
 */ export const K8sSpecs = { displayName : "Kubernetes specs" , name : K8sSpecsType , extract : createK8sSpecsFingerprints , apply : applyK8sSpecsFingerprint , summary : diffK8sSpecsFingerprints , toDisplayableFingerprint : fp => k8sSpecsFingerprintDisplayName ( k8sSpecsFingerprintName ( fp . data ) ) , toDisplayableFingerprintName : k8sSpecsFingerprintDisplayName , workflows : [ DefaultTargetDiffHandler , ] , } ;