import * as _ from 'lodash' ; import { Kubectl } from "../kubectl" ; import { KUBERNETES_SCHEMA_ENUM_FILE , FALLBACK_SCHEMA_FILE , KUBERNETES_GROUP_VERSION_KIND } from "./yaml-constant" ; import { formatComplex , formatOne , formatType } from '../schema-formatting' ; import * as swagger from '../components/swagger/swagger' ; import { succeeded } from '../errorable' ; import * as util from "./yaml-util" ; interface KubernetesSchema { readonly name : string ; readonly description ? : string ; readonly id ? : string ; readonly apiVersion ? : string ; readonly kind ? : string ; readonly 'x-kubernetes-group-version-kind' ? : any [ ] ; properties ? : { [ key ] : any ; } ; } export class KubernetesClusterSchemaHolder { private definitions : { [ key ] : KubernetesSchema ; } = { } ; private schemaEnums : { [ key ] : { [ key ] : [ string [ ] ] ; } ; } ; private crdSchemas : { [ key ] : object | undefined ; } ; public static async fromActiveCluster ( kubectl ) { const holder = new KubernetesClusterSchemaHolder ( ) ; await holder . loadSchemaFromActiveCluster ( kubectl , KUBERNETES_SCHEMA_ENUM_FILE ( ) ) ; return holder ; } public static fallback ( ) { const holder = new KubernetesClusterSchemaHolder ( ) ; const fallbackSchema = util . loadJson ( FALLBACK_SCHEMA_FILE ( ) ) ; holder . loadSchemaFromRaw ( fallbackSchema , KUBERNETES_SCHEMA_ENUM_FILE ( ) , undefined ) ; return holder ; } private async loadSchemaFromActiveCluster ( kubectl , schemaEnumFile ? ) { const clusterSwagger = await swagger . getClusterSwagger ( kubectl ) ; const crdSchemas = await swagger . getCrdSchemas ( kubectl ) ; if ( crdSchemas ) { this . crdSchemas = crdSchemas ; } const schemaRaw = succeeded ( clusterSwagger ) ? this . definitionsObject ( clusterSwagger . result ) : util . loadJson ( FALLBACK_SCHEMA_FILE ( ) ) ; this . loadSchemaFromRaw ( schemaRaw , schemaEnumFile , kubectl ) ; } private definitionsObject ( swagger ) { return { definitions : swagger . definitions } ; } private loadSchemaFromRaw ( schemaRaw , schemaEnumFile ? , kubectl ? ) { this . schemaEnums = schemaEnumFile ? util . loadJson ( schemaEnumFile ) : { } ; const definitions = schemaRaw . definitions ; makeSafeIntOrString ( definitions ) ; for ( const name of Object . keys ( definitions ) ) { this . saveSchemaWithManifestStyleKeys ( name , definitions [ name ] ) ; } for ( const schema of _ . values ( this . definitions ) ) { if ( kubectl && this . crdSchemas && schema . id && schema . id . toLowerCase ( ) in this . crdSchemas ) { const crdProperties = this . crdSchemas [ schema . id . toLowerCase ( ) ] ; if ( crdProperties ) { schema . properties = crdProperties ; } } if ( schema . properties ) { _ . each ( schema . properties , ( propVal , propKey ) => { if ( schema . kind && propKey === 'kind' ) { propVal . markdownDescription = this . getMarkdownDescription ( schema . kind , undefined , schema , true ) ; return ; } const currentPropertyTypeRef = propVal . $ref || ( propVal . items ? propVal . items . $ref : undefined ) ; if ( _ . isString ( currentPropertyTypeRef ) ) { const id = getNameInDefinitions ( currentPropertyTypeRef ) ; const propSchema = this . lookup ( id ) ; if ( propSchema ) { propVal . markdownDescription = this . getMarkdownDescription ( propKey , propVal , propSchema ) ; } } else { propVal . markdownDescription = this . getMarkdownDescription ( propKey , propVal , undefined ) ; } } ) ; this . replaceDefinitionRefsWithYamlSchemaUris ( schema . properties ) ; this . loadEnumsForKubernetesSchema ( schema ) ; } } } public lookup ( key ) { return key ? this . definitions [ key . toLowerCase ( ) ] : undefined ; } param { string } name param originalSchema private saveSchemaWithManifestStyleKeys ( name , originalSchema ) { if ( isGroupVersionKindStyle ( originalSchema ) ) { getManifestStyleSchemas ( originalSchema ) . forEach ( ( schema ) => { this . saveSchema ( { ... schema , name } ) ; } ) ; } else { this . saveSchema ( { name , ... originalSchema } ) ; } } private replaceDefinitionRefsWithYamlSchemaUris ( node ) { if ( ! node ) { return ; } if ( _ . isArray ( node ) ) { for ( const subItem of < any [ ] > node ) { this . replaceDefinitionRefsWithYamlSchemaUris ( subItem ) ; } } if ( ! _ . isObject ( node ) ) { return ; } for ( const key of Object . keys ( node ) ) { this . replaceDefinitionRefsWithYamlSchemaUris ( node [ key ] ) ; } if ( _ . isString ( node . $ref ) ) { const name = getNameInDefinitions ( node . $ref ) ; const schema = this . lookup ( name ) ; if ( schema ) { node . $ref = util . makeKubernetesUri ( schema . name ) ; } } } private loadEnumsForKubernetesSchema ( node ) { if ( node . properties && this . schemaEnums [ node . name ] ) { _ . each ( node . properties , ( propSchema , propKey ) => { if ( this . schemaEnums [ node . name ] [ propKey ] ) { if ( propSchema . type === "array" && propSchema . items ) { propSchema . items . enum = this . schemaEnums [ node . name ] [ propKey ] ; } else { propSchema . enum = this . schemaEnums [ node . name ] [ propKey ] ; } } } ) ; } } private saveSchema ( schema ) { if ( schema . name ) { this . definitions [ schema . name . toLowerCase ( ) ] = schema ; } if ( schema . id ) { this . definitions [ schema . id . toLowerCase ( ) ] = schema ; } } private getMarkdownDescription ( currentPropertyName , currentProperty , targetSchema , isKind = false ) { if ( isKind ) { return formatComplex ( currentPropertyName , targetSchema . description , undefined , targetSchema . properties ) ; } if ( ! targetSchema ) { return formatOne ( currentPropertyName , formatType ( currentProperty ) , currentProperty . description ) ; } const properties = targetSchema . properties ; if ( properties ) { return formatComplex ( currentPropertyName , currentProperty ? currentProperty . description : "" , targetSchema . description , properties ) ; } return currentProperty ? currentProperty . description : ( targetSchema ? targetSchema . description : "" ) ; } } param originalSchema return function isGroupVersionKindStyle ( originalSchema ) { return originalSchema [ KUBERNETES_GROUP_VERSION_KIND ] && originalSchema [ KUBERNETES_GROUP_VERSION_KIND ] . length ; } param originalSchema returns { KubernetesSchema [ ] } function getManifestStyleSchemas ( originalSchema ) { const schemas = Array . of < KubernetesSchema > ( ) ; const groupKindNode = originalSchema [ KUBERNETES_GROUP_VERSION_KIND ] ; delete originalSchema [ KUBERNETES_GROUP_VERSION_KIND ] ; groupKindNode . forEach ( ( groupKindNode ) => { const gvk = util . parseKubernetesGroupVersionKind ( groupKindNode ) ; if ( ! gvk ) { return ; } const { id , apiVersion , kind } = gvk ; schemas . push ( { id , apiVersion , kind , ... originalSchema } ) ; } ) ; return schemas ; } function getNameInDefinitions ( $ref ) { const prefix = '#/definitions/' ; if ( $ref . startsWith ( prefix ) ) { return $ref . slice ( prefix . length ) ; } else { return prefix ; } } function makeSafeIntOrString ( definitions ) { const intOrStringDefinition = definitions [ 'io.k8s.apimachinery.pkg.util.intstr.IntOrString' ] ; if ( intOrStringDefinition ) { if ( intOrStringDefinition . type === 'string' ) { intOrStringDefinition . oneOf = [ { type : 'string' } , { type : 'integer' } ] ; delete intOrStringDefinition . type ; } } }