'use strict' ; import request = require ( 'request' ) ; import * as kubernetes from '@kubernetes/client-node' ; import * as pluralize from 'pluralize' ; import { formatComplex , formatOne , Typed , formatType } from "./schema-formatting" ; import { loadKubeconfig } from './components/kubectl/kubeconfig' ; export interface SwaggerModel { readonly definitions : any [ ] ; } export async function readSwagger ( ) { return readSwaggerCore ( await loadKubeconfig ( ) ) ; } function readSwaggerCore ( kc ) { const currentCluster = kc . getCurrentCluster ( ) ; if ( ! currentCluster ) { return new Promise ( ( resolve ) => resolve ( undefined ) ) ; } const uri = `${ currentCluster . server }/swagger.json` ; const opts = { url : uri , } ; kc . applyToRequest ( opts ) ; return new Promise ( ( resolve , reject ) => { request ( uri , opts , ( error , response , body ) => { if ( error ) { reject ( error ) ; return ; } if ( response . statusCode !== 200 ) { reject ( response ) ; return ; } try { const obj = JSON . parse ( body ) ; resolve ( obj ) ; } catch ( ex ) { console . log ( ex ) ; } } ) ; } ) ; } export function readExplanation ( swagger , fieldsPath ) { const fields = fieldsPath . split ( '.' ) ; const kindName = fields . shift ( ) ! ; const kindDef = findKindModel ( swagger , kindName ) ; if ( ! kindDef ) { return `No explanation available for ${ fieldsPath }` ; } const text = chaseFieldPath ( swagger , kindDef , kindName , fields ) ; return text ; } function findKindModel ( swagger , kindName ) { const v1def = findProperty ( swagger . definitions , 'v1.' + kindName ) ; const v1beta1def = findProperty ( swagger . definitions , 'v1beta1.' + kindName ) ; const kindDef = v1def || v1beta1def ; return kindDef ; } function chaseFieldPath ( swagger , currentProperty , currentPropertyName , fields ) { const currentPropertyTypeRef = currentProperty . $ref || ( currentProperty . items ? currentProperty . items . $ref : undefined ) ; if ( currentPropertyTypeRef ) { const typeDefnPath = currentPropertyTypeRef . split ( '/' ) ; typeDefnPath . shift ( ) ; const currentPropertyTypeInfo = findTypeDefinition ( swagger , typeDefnPath ) ; if ( currentPropertyTypeInfo ) { const typeRefProperties = currentPropertyTypeInfo . properties ; if ( typeRefProperties ) { if ( fields . length === 0 ) { return formatComplex ( currentPropertyName , currentProperty . description , currentPropertyTypeInfo . description , typeRefProperties ) ; } else { const nextField = fields . shift ( ) ! ; const nextProperty = findProperty ( typeRefProperties , nextField ) ; if ( nextProperty ) { return chaseFieldPath ( swagger , nextProperty , nextField , fields ) ; } else { return explainError ( nextField , 'field does not exist' ) ; } } } else { return formatOne ( currentPropertyName , formatType ( currentPropertyTypeInfo ) , currentProperty . description ) ; } } else { return explainError ( currentPropertyTypeRef , 'unresolvable type reference' ) ; } } else { const properties = currentProperty . properties ; if ( properties ) { if ( fields . length === 0 ) { return formatComplex ( currentPropertyName , currentProperty . description , undefined , properties ) ; } else { const nextField = fields . shift ( ) ! ; const nextProperty = findProperty ( properties , nextField ) ; if ( nextProperty ) { return chaseFieldPath ( swagger , nextProperty , nextField , fields ) ; } else { return explainError ( nextField , 'field does not exist' ) ; } } } else { return formatOne ( currentPropertyName , formatType ( currentProperty ) , currentProperty . description ) ; } } } function explainError ( header , error ) { return `**${ header }:** ${ error }` ; } function singularizeVersionedName ( name ) { const bits = name . split ( '.' ) ; let lastBit = bits . pop ( ) ! ; lastBit = pluralize . singular ( lastBit ) ; bits . push ( lastBit ) ; return bits . join ( '.' ) ; } function findProperty ( obj , name ) { const n = name . toLowerCase ( ) ; for ( const p in obj ) { const pinfo = obj [ p ] ; if ( p . toLowerCase ( ) === n ) { return pinfo ; } const gvks = pinfo [ "x-kubernetes-group-version-kind" ] ; if ( gvks && gvks . length && gvks . length > 0 && gvks [ 0 ] ) { const gvk = gvks [ 0 ] ; const ver = gvk . version ; const kind = gvk . kind ; if ( ver && kind ) { const vk = `${ ver }.${ kind }` ; if ( vk . toLowerCase ( ) === n ) { return pinfo ; } } } } const singname = singularizeVersionedName ( name ) ; if ( singname === name ) { return undefined ; } else { return findProperty ( obj , singname ) ; } } function findTypeDefinition ( swagger , typeDefnPath ) { let m = swagger ; for ( const p of typeDefnPath ) { m = findProperty ( m , p ) ; if ( ! m ) { return undefined ; } } return m ; } interface TypeModel extends Typed { readonly description ? : string ; readonly properties ? : any ; }