import Table from "cli-table" ; import commander from "commander" ; import { duration , getDeploymentsBasedOnFilters , IDeployment , status as getDeploymentStatus , fetchPR , fetchAuthor , getRepositoryFromURL , } from "spektate/lib/IDeployment" ; import AzureDevOpsPipeline from "spektate/lib/pipeline/AzureDevOpsPipeline" ; import { getManifestSyncState as getAzureManifestSyncState , IAzureDevOpsRepo , } from "spektate/lib/repository/IAzureDevOpsRepo" ; import { getManifestSyncState as getGithubManifestSyncState , IGitHub , } from "spektate/lib/repository/IGitHub" ; import { ITag } from "spektate/lib/repository/Tag" ; import { Config } from "../../config" ; import { build as buildCmd , exit as exitCmd } from "../../lib/commandBuilder" ; import { build as buildError , log as logError } from "../../lib/errorBuilder" ; import { errorStatusCode } from "../../lib/errorStatusCode" ; import { isIntegerString } from "../../lib/validator" ; import { logger } from "../../logger" ; import decorator from "./get.decorator.json" ; import { IPullRequest } from "spektate/lib/repository/IPullRequest" ; import { IAuthor } from "spektate/lib/repository/Author" ; import { DeploymentRow , printDeploymentTable , } from "../../lib/azure/deploymenttable" ; const promises = [ ] ; const pullRequests = { } ; const authors = { } ; /**
 * Output formats to display service details
 */ export enum OUTPUT_FORMAT { NORMAL = 0 , WIDE = 1 , JSON = 2 } /**
 * Interface for capturing all the
 * objects and key during the initialization
 * process
 */ export interface InitObject { accountName : string ; tableName : string ; partitionKey : string ; clusterPipeline : AzureDevOpsPipeline ; hldPipeline : AzureDevOpsPipeline ; key : string ; srcPipeline : AzureDevOpsPipeline ; manifestRepo ? : string ; accessToken ? : string ; } /**
 * Command Line values from the commander
 */ export interface CommandOptions { watch : boolean ; output : string ; ring : string ; imageTag : string ; buildId : string ; commitId : string ; service : string ; deploymentId : string ; top : string ; hideSeparators : boolean ; } /**
 * Validated commandline values. After verify top value and
 * the output format.
 */ export interface ValidatedOptions extends CommandOptions { nTop : number ; outputFormat : OUTPUT_FORMAT ; } param outputFormat export const processOutputFormat = ( outputFormat ) : OUTPUT_FORMAT => { if ( outputFormat && outputFormat . toLowerCase ( ) === "wide" ) { return OUTPUT_FORMAT . WIDE ; } if ( outputFormat && outputFormat . toLowerCase ( ) === "json" ) { return OUTPUT_FORMAT . JSON ; } return OUTPUT_FORMAT . NORMAL ; } ; param opts return throws export const validateValues = ( opts ) : ValidatedOptions => { let top = 0 ; if ( opts . top ) { if ( isIntegerString ( opts . top ) ) { top = parseInt ( opts . top , 10 ) ; } else { throw buildError ( errorStatusCode . VALIDATION_ERR , { errorKey : "introspect-get-cmd-err-validation-top-num" , values : [ opts . top ] , } ) ; } } return { buildId : opts . buildId , commitId : opts . commitId , deploymentId : opts . deploymentId , ring : opts . ring , imageTag : opts . imageTag , nTop : top , output : opts . output , outputFormat : processOutputFormat ( opts . output ) , service : opts . service , top : opts . top , watch : opts . watch , hideSeparators : opts . hideSeparators , } ; } ; /**
 * Initializes the pipelines assuming that the configuration has been loaded
 */ export const initialize = async ( ) : Promise < InitObject > => { const config = Config ( ) ; if ( ! config . introspection || ! config . azure_devops || ! config . introspection . azure || ! config . azure_devops . org || ! config . azure_devops . project || ! config . introspection . azure . account_name || ! config . introspection . azure . table_name || ! config . introspection . azure . key || ! config . introspection . azure . partition_key || ! config . introspection . azure . key ) { throw buildError ( errorStatusCode . VALIDATION_ERR , "introspect-get-cmd-missing-vals" ) ; } return { clusterPipeline : new AzureDevOpsPipeline ( config . azure_devops . org , config . azure_devops . project , config . azure_devops . access_token ) , hldPipeline : new AzureDevOpsPipeline ( config . azure_devops . org , config . azure_devops . project , config . azure_devops . access_token ) , key : config . introspection . azure . key , srcPipeline : new AzureDevOpsPipeline ( config . azure_devops . org , config . azure_devops . project , config . azure_devops . access_token ) , accountName : config . introspection . azure . account_name , tableName : config . introspection . azure . table_name , partitionKey : config . introspection . azure . partition_key , manifestRepo : config . azure_devops . manifest_repository , accessToken : config . azure_devops . access_token , } ; } ; param initObj export const getClusterSyncStatuses = async ( initObj ) : Promise < ITag [ ] | undefined > => { try { if ( initObj . manifestRepo && initObj . manifestRepo . includes ( "azure.com" ) ) { const manifestUrlSplit = initObj . manifestRepo . split ( "/" ) ; const manifestRepo = { org : manifestUrlSplit [ 3 ] , project : manifestUrlSplit [ 4 ] , repo : manifestUrlSplit [ 6 ] , } ; return await getAzureManifestSyncState ( manifestRepo , initObj . accessToken ) ; } else if ( initObj . manifestRepo && initObj . manifestRepo . includes ( "github.com" ) ) { const manifestUrlSplit = initObj . manifestRepo . split ( "/" ) ; const manifestRepo = { reponame : manifestUrlSplit [ 4 ] , username : manifestUrlSplit [ 3 ] , } ; return await getGithubManifestSyncState ( manifestRepo , initObj . accessToken ) ; } else { return undefined ; } } catch ( err ) { logger . verbose ( `Could not get cluster sync status ` + err ) ; } } ; param deployment param initObj export const fetchAuthorInformation = async ( deployment , initObj ) : Promise < void > => { let commitId = deployment . srcToDockerBuild ?. sourceVersion || deployment . hldToManifestBuild ?. sourceVersion ; let repo = deployment . srcToDockerBuild ?. repository || deployment . hldToManifestBuild ?. repository ; if ( ! repo && deployment . sourceRepo ) { repo = getRepositoryFromURL ( deployment . sourceRepo ) ; commitId = deployment . srcToDockerBuild ?. sourceVersion ; } if ( ! repo && deployment . hldRepo ) { repo = getRepositoryFromURL ( deployment . hldRepo ) ; commitId = deployment . hldToManifestBuild ?. sourceVersion ; } if ( repo && commitId !== "" ) { try { const author = await fetchAuthor ( repo , commitId ! , initObj . accessToken ) ; if ( author ) { authors [ deployment . deploymentId ] = author ; } } catch ( err ) { logger . verbose ( `Could not get author for ${ commitId }: ` + err ) ; } } } ; param deployment param initObj export const fetchPRInformation = async ( deployment , initObj ) : Promise < void > => { if ( deployment . hldRepo && deployment . pr ) { const repo = getRepositoryFromURL ( deployment . hldRepo ) ; const strPr = deployment . pr . toString ( ) ; if ( repo ) { try { const pr = await fetchPR ( repo , strPr , initObj . accessToken ) ; if ( pr ) { pullRequests [ deployment . deploymentId ] = pr ; } } catch ( err ) { logger . verbose ( `Could not get PR ${ strPr } information: ` + err ) ; } } } } ; param deployments param initObj export const getAuthors = ( deployments , initObj ) : void => { ( deployments || [ ] ) . forEach ( ( d ) => { promises . push ( fetchAuthorInformation ( d , initObj ) ) ; } ) ; } ; param deployments param initObj export const getPRs = ( deployments , initObj ) : void => { ( deployments || [ ] ) . forEach ( ( d ) => { promises . push ( fetchPRInformation ( d , initObj ) ) ; } ) ; } ; param status return export const getStatus = ( status ? ) : string => { if ( ! status ) { return "" ; } else if ( status === "succeeded" ) { return "\u2713" ; } return "\u0445" ; } ; param deployment param syncStatuses export const getClusterSyncStatusForDeployment = ( deployment , syncStatuses ) : ITag | undefined => { return syncStatuses . find ( ( tag ) => tag . commit === deployment . manifestCommitId ) ; } ; param deployments param outputFormat export const printDeployments = ( deployments , outputFormat , limit ? , syncStatuses ? , removeSeparators ? ) : Table | undefined => { const deploymentsToDisplay = [ ] ; if ( deployments && deployments . length > 0 ) { const toDisplay = limit ? deployments . slice ( 0 , limit ) : deployments ; toDisplay . forEach ( ( deployment ) => { const row = [ ] ; const deploymentToDisplay = { status : getDeploymentStatus ( deployment ) , service : deployment . service , env : deployment . environment , author : deployment . deploymentId in authors ? authors [ deployment . deploymentId ] . name : undefined , srctoAcrCommitId : deployment . commitId , srcToAcrPipelineId : deployment . srcToDockerBuild ?. id , imageTag : deployment . imageTag , srcToAcrResult : getStatus ( deployment . srcToDockerBuild ?. result ) , AcrToHldPipelineId : deployment . dockerToHldRelease ? deployment . dockerToHldRelease . id : deployment . dockerToHldReleaseStage ? deployment . dockerToHldReleaseStage . id : undefined , AcrToHldResult : getStatus ( deployment . dockerToHldRelease ?. status || deployment . dockerToHldReleaseStage ?. result ) , AcrToHldCommitId : deployment . hldCommitId , pr : deployment . deploymentId in pullRequests ? pullRequests [ deployment . deploymentId ] . id . toString ( ) : undefined , mergedBy : deployment . deploymentId in pullRequests ? pullRequests [ deployment . deploymentId ] . mergedBy ?. name : undefined , HldToManifestPipelineId : deployment . hldToManifestBuild ?. id , HldToManifestResult : getStatus ( deployment . hldToManifestBuild ?. result ) , HldToManifestCommitId : deployment . manifestCommitId , duration : duration ( deployment ) + " mins" , startTime : deployment . srcToDockerBuild ?. startTime ?. toLocaleString ( ) || deployment . hldToManifestBuild ?. startTime ?. toLocaleString ( ) , syncStatus : syncStatuses ? getClusterSyncStatusForDeployment ( deployment , syncStatuses ) ?. name : undefined , } ; if ( deploymentToDisplay . pr && ! deploymentToDisplay . mergedBy ) { deploymentToDisplay . status = "Waiting" ; } deploymentsToDisplay . push ( deploymentToDisplay ) ; } ) ; return printDeploymentTable ( outputFormat , deploymentsToDisplay , removeSeparators , deploymentsToDisplay . filter ( ( d ) => d . syncStatus ) . length > 0 ) ; } else { logger . info ( "No deployments found for specified filters." ) ; return undefined ; } } ; param values param deployments param syncStatuses param initObj export const displayDeployments = async ( values , deployments , syncStatuses , initObj ) : Promise < Table | undefined > => { if ( deployments && values . nTop ) { deployments = deployments . slice ( 0 , values . nTop ) ; } if ( values . outputFormat === OUTPUT_FORMAT . WIDE ) { getPRs ( deployments , initObj ) ; getAuthors ( deployments , initObj ) ; } if ( values . outputFormat === OUTPUT_FORMAT . JSON ) { console . log ( JSON . stringify ( deployments , null , 2 ) ) ; return deployments || [ ] ; } await Promise . all ( promises ) ; return printDeployments ( deployments , values . outputFormat , values . nTop , syncStatuses , values . hideSeparators ) ; } ; param initObj param values export const getDeployments = async ( initObj , values ) : Promise < IDeployment [ ] | undefined > => { try { const syncStatusesPromise = getClusterSyncStatuses ( initObj ) ; const deploymentsPromise = getDeploymentsBasedOnFilters ( initObj . accountName , initObj . key , initObj . tableName , initObj . partitionKey , initObj . srcPipeline , initObj . hldPipeline , initObj . clusterPipeline , values . ring , values . imageTag , values . buildId , values . commitId , values . service , values . deploymentId ) ; const tuple = await Promise . all ( [ deploymentsPromise , syncStatusesPromise ] ) ; const deployments = tuple [ 0 ] ; const syncStatuses = tuple [ 1 ] ; await displayDeployments ( values , deployments , syncStatuses , initObj ) ; return deployments ; } catch ( err ) { throw buildError ( errorStatusCode . EXE_FLOW_ERR , "introspect-get-cmd-get-deployments-err" , err ) ; } } ; param initObject param values export const watchGetDeployments = async ( initObjects , values ) : Promise < void > => { const timeInterval = 5000 ; await getDeployments ( initObjects , values ) ; setInterval ( async ( ) => { await getDeployments ( initObjects , values ) ; } , timeInterval ) ; } ; param opts param exitFn export const execute = async ( opts , exitFn ) : Promise < void > => { try { const values = validateValues ( opts ) ; const initObjects = await initialize ( ) ; if ( opts . watch ) { await watchGetDeployments ( initObjects , values ) ; } else { await getDeployments ( initObjects , values ) ; await exitFn ( 0 ) ; } } catch ( err ) { logError ( buildError ( errorStatusCode . CMD_EXE_ERR , "introspect-get-cmd-failed" , err ) ) ; await exitFn ( 1 ) ; } } ; param command export const commandDecorator = ( command ) : void => { buildCmd ( command , decorator ) . action ( async ( opts ) => { await execute ( opts , async ( status ) => { await exitCmd ( logger , process . exit , status ) ; } ) ; } ) ; } ;