import * as azure from "azure-storage" ; import uuid from "uuid/v4" ; import { logger } from "../../logger" ; import { build as buildError } from "../errorBuilder" ; import { errorStatusCode } from "../errorStatusCode" ; import { OUTPUT_FORMAT } from "../../commands/deployment/get" ; import Table from "cli-table" ; /**
 * Deployment Table interface to hold necessary information about a table for deployments
 */ export interface DeploymentTable { accountName : string ; accountKey : string ; tableName : string ; partitionKey : string ; } export interface DeploymentEntry { RowKey : string ; PartitionKey : string ; commitId ? : string ; env ? : string ; imageTag ? : string ; p1 ? : string ; service ? : string ; p2 ? : string ; hldCommitId ? : string ; p3 ? : string ; manifestCommitId ? : string ; sourceRepo ? : string ; hldRepo ? : string ; manifestRepo ? : string ; pr ? : string ; } export interface DeploymentRow { status ? : string ; service ? : string ; env ? : string ; author ? : string ; imageTag ? : string ; srcToAcrPipelineId ? : string ; srcToAcrResult ? : string ; srctoAcrCommitId ? : string ; AcrToHldPipelineId ? : string ; AcrToHldResult ? : string ; AcrToHldCommitId ? : string ; HldToManifestPipelineId ? : string ; HldToManifestResult ? : string ; HldToManifestCommitId ? : string ; pr ? : string ; mergedBy ? : string ; duration ? : string ; startTime ? : string ; syncStatus ? : string ; } export interface TableHeader { title ? : string ; alignment ? : "left" | "middle" | "right" ; } param outputFormat param deployments param removeSeparators export const printDeploymentTable = ( outputFormat , deployments , removeSeparators ? , clusterSyncAvailable ? ) : Table => { const tableHeaders = [ outputFormat === OUTPUT_FORMAT . WIDE ? { title : "Start Time" } : { } , { title : "Status" } , { title : "Service" } , { title : "Ring" } , outputFormat === OUTPUT_FORMAT . WIDE ? { title : "Author" } : { } , { title : "Image Tag" } , ! removeSeparators ? { title : "│" } : { } , { title : "Image Creation" , alignment : "right" } , { title : "Commit" } , { title : "OK" } , ! removeSeparators ? { title : "│" } : { } , { title : "Metadata Update" , alignment : "right" } , { title : "Commit" } , { title : "OK" } , ! removeSeparators ? { title : "│" } : { } , outputFormat === OUTPUT_FORMAT . WIDE ? { title : "Approval PR" , alignment : "right" } : { } , outputFormat === OUTPUT_FORMAT . WIDE ? { title : "Merged By" } : { } , { title : "Ready to Deploy" , alignment : "right" } , { title : "Commit" } , { title : "OK" } , ! removeSeparators ? { title : "│" } : { } , { title : "Duration" , alignment : "right" } , outputFormat === OUTPUT_FORMAT . WIDE && clusterSyncAvailable ? { title : "Cluster Sync" } : { } , ] ; const columnAlignment = [ ] ; const headers = [ ] ; tableHeaders . forEach ( ( header ) => { if ( header . title ) { headers . push ( header . title ) ; columnAlignment . push ( header . alignment ? header . alignment : "left" ) ; } } ) ; const table = new Table ( { head : headers , chars : { top : "" , "top-mid" : "" , "top-left" : "" , "top-right" : "" , bottom : "" , "bottom-mid" : "" , "bottom-left" : "" , "bottom-right" : "" , left : "" , "left-mid" : "" , mid : "" , "mid-mid" : "" , right : "" , "right-mid" : "" , middle : " " , } , style : { "padding-left" : 0 , "padding-right" : 0 } , colAligns : columnAlignment , } ) ; deployments . forEach ( ( deployment ) => { const row = [ ] ; if ( outputFormat === OUTPUT_FORMAT . WIDE ) { row . push ( deployment . startTime ?? "" ) ; } row . push ( deployment . status ?? "" ) ; row . push ( deployment . service ?? "" ) ; row . push ( deployment . env ?? "" ) ; if ( outputFormat === OUTPUT_FORMAT . WIDE ) { row . push ( deployment . author ?? "" ) ; } row . push ( deployment . imageTag ?? "" ) ; if ( ! removeSeparators ) row . push ( "│" ) ; row . push ( deployment . srcToAcrPipelineId ?? "" ) ; row . push ( deployment . srctoAcrCommitId ?? "" ) ; row . push ( deployment . srcToAcrResult ?? "" ) ; if ( ! removeSeparators ) row . push ( "│" ) ; row . push ( deployment . AcrToHldPipelineId ?? "" ) ; row . push ( deployment . AcrToHldCommitId ?? "" ) ; row . push ( deployment . AcrToHldResult ?? "" ) ; if ( ! removeSeparators ) row . push ( "│" ) ; if ( outputFormat === OUTPUT_FORMAT . WIDE ) { row . push ( deployment . pr ?? "" ) ; row . push ( deployment . mergedBy ?? "" ) ; } row . push ( deployment . HldToManifestPipelineId ?? "" ) ; row . push ( deployment . HldToManifestCommitId ?? "" ) ; row . push ( deployment . HldToManifestResult ?? "" ) ; if ( ! removeSeparators ) row . push ( "│" ) ; row . push ( deployment . duration ?? "" ) ; if ( outputFormat === OUTPUT_FORMAT . WIDE && clusterSyncAvailable ) { row . push ( deployment . syncStatus ?? "" ) ; } table . push ( row ) ; } ) ; console . log ( table . toString ( ) ) ; return table ; } ; /**
 * Generates a RowKey GUID 12 characters long
 */ export const getRowKey = ( ) : string => { return uuid ( ) . replace ( "-" , "" ) . substring ( 0 , 12 ) ; } ; param tableInfo export const getTableService = ( tableInfo ) : azure . TableService => { return azure . createTableService ( tableInfo . accountName , tableInfo . accountKey ) ; } ; param tableInfo param filterName param filterValue export const findMatchingDeployments = ( tableInfo , filterName , filterValue ) : Promise < DeploymentEntry [ ] > => { const tableService = getTableService ( tableInfo ) ; const query = new azure . TableQuery ( ) . where ( `PartitionKey eq '${ tableInfo . partitionKey }'` ) ; query . and ( `${ filterName } eq '${ filterValue }'` ) ; const nextContinuationToken = null ; return new Promise ( ( resolve , reject ) => { tableService . queryEntities < DeploymentEntry > ( tableInfo . tableName , query , nextContinuationToken , ( error , result ) => { if ( ! error ) { resolve ( result . entries ) ; } else { reject ( error ) ; } } ) ; } ) ; } ; param tableInfo param entry export const insertToTable = ( tableInfo , entry ) : Promise < void > => { const tableService = getTableService ( tableInfo ) ; return new Promise ( ( resolve , reject ) => { tableService . insertEntity ( tableInfo . tableName , entry , ( err ) => { if ( ! err ) { resolve ( ) ; } else { reject ( err ) ; } } ) ; } ) ; } ; param tableInfo param entry export const deleteFromTable = ( tableInfo , entry ) : Promise < void > => { const tableService = getTableService ( tableInfo ) ; return new Promise ( ( resolve , reject ) => { tableService . deleteEntity ( tableInfo . tableName , entry , { } , ( err ) => { if ( ! err ) { resolve ( ) ; } else { reject ( err ) ; } } ) ; } ) ; } ; param tableInfo param entry export const updateEntryInTable = ( tableInfo , entry ) : Promise < void > => { const tableService = getTableService ( tableInfo ) ; return new Promise ( ( resolve , reject ) => { tableService . replaceEntity ( tableInfo . tableName , entry , ( err ) => { if ( ! err ) { resolve ( ) ; } else { reject ( err ) ; } } ) ; } ) ; } ; param tableInfo param pipelineId param imageTag param serviceName param commitId export const addSrcToACRPipeline = async ( tableInfo , pipelineId , imageTag , serviceName , commitId , repository ? ) : Promise < DeploymentEntry > => { try { const entry = { PartitionKey : tableInfo . partitionKey , RowKey : getRowKey ( ) , commitId , imageTag , p1 : pipelineId , service : serviceName , } ; if ( repository ) { entry . sourceRepo = repository . toLowerCase ( ) ; } await insertToTable ( tableInfo , entry ) ; logger . info ( "Added first pipeline details to the database" ) ; return entry ; } catch ( err ) { throw buildError ( errorStatusCode . AZURE_STORAGE_OP_ERR , "deployment-table-add-src-to-acr-pipeline" , err ) ; } } ; param entries param tableInfo param pipelineId param imageTag param hldCommitId param env param pr export const updateMatchingACRToHLDPipelineEntry = async ( entries , tableInfo , pipelineId , imageTag , hldCommitId , env , pr ? , repository ? ) : Promise < DeploymentEntry | null > => { const found = ( entries || [ ] ) . find ( ( entry ) => { return ( ( entry . p2 ? entry . p2 === pipelineId : true ) && ( entry . hldCommitId ? entry . hldCommitId === hldCommitId : true ) && ( entry . env ? entry . env === env : true ) ) ; } ) ; if ( found ) { const updateEntry = { PartitionKey : found . PartitionKey , RowKey : found . RowKey , commitId : found . commitId , env : env . toLowerCase ( ) , hldCommitId : hldCommitId . toLowerCase ( ) , imageTag : found . imageTag , p1 : found . p1 , p2 : pipelineId . toLowerCase ( ) , service : found . service , sourceRepo : found . sourceRepo , } ; if ( pr ) { updateEntry . pr = pr . toLowerCase ( ) ; } if ( repository ) { updateEntry . hldRepo = repository . toLowerCase ( ) ; } await updateEntryInTable ( tableInfo , updateEntry ) ; logger . info ( `Updated p2 entry for imageTag ${ imageTag } by finding corresponding entry` ) ; return updateEntry ; } return null ; } ; param tableInfo param pipelineId param imageTag param hldCommitId param env param pr export const addNewRowToACRToHLDPipelines = async ( tableInfo , pipelineId , imageTag , hldCommitId , env , pr ? , repository ? , similarEntry ? ) : Promise < DeploymentEntry > => { const newEntry = { PartitionKey : tableInfo . partitionKey , RowKey : getRowKey ( ) , commitId : similarEntry ?. commitId ? similarEntry . commitId : "" , env : env . toLowerCase ( ) , hldCommitId : hldCommitId . toLowerCase ( ) , imageTag : imageTag . toLowerCase ( ) , p1 : similarEntry ?. p1 ? similarEntry . p1 : "" , p2 : pipelineId . toLowerCase ( ) , service : similarEntry ?. service ? similarEntry . service : "" , sourceRepo : similarEntry ?. sourceRepo ? similarEntry . sourceRepo : "" , } ; if ( pr ) { newEntry . pr = pr . toLowerCase ( ) ; } if ( repository ) { newEntry . hldRepo = repository . toLowerCase ( ) ; } await insertToTable ( tableInfo , newEntry ) ; logger . info ( `Added new p2 entry for imageTag ${ imageTag } - ${ similarEntry ? "by finding a similar entry" : "no matching entry was found." }` ) ; return newEntry ; } ; param tableInfo param pipelineId param imageTag param hldCommitId param env export const updateACRToHLDPipeline = async ( tableInfo , pipelineId , imageTag , hldCommitId , env , pr ? , repository ? ) : Promise < DeploymentEntry > => { try { const entries = await findMatchingDeployments ( tableInfo , "imageTag" , imageTag ) ; if ( entries && entries . length > 0 ) { const found = await updateMatchingACRToHLDPipelineEntry ( entries , tableInfo , pipelineId , imageTag , hldCommitId , env , pr , repository ) ; if ( found ) { return found ; } return await addNewRowToACRToHLDPipelines ( tableInfo , pipelineId , imageTag , hldCommitId , env , pr , repository , entries [ entries . length - 1 ] ) ; } return await addNewRowToACRToHLDPipelines ( tableInfo , pipelineId , imageTag , hldCommitId , env , pr , repository ) ; } catch ( err ) { throw buildError ( errorStatusCode . AZURE_STORAGE_OP_ERR , "deployment-table-add-acr-to-hld-pipeline" , err ) ; } } ; param entries param tableInfo param hldCommitId param pipelineId param manifestCommitId param pr export const updateHLDtoManifestEntry = async ( entries , tableInfo , hldCommitId , pipelineId , manifestCommitId ? , pr ? , repository ? ) : Promise < DeploymentEntry | null > => { const found = entries . find ( ( entry ) => ( entry . p3 ? entry . p3 === pipelineId : true ) && ( entry . manifestCommitId ? entry . manifestCommitId === manifestCommitId : true ) ) ; if ( found ) { const entry = { PartitionKey : found . PartitionKey , RowKey : found . RowKey , commitId : found . commitId , env : found . env , hldCommitId , hldRepo : found . hldRepo , imageTag : found . imageTag , p1 : found . p1 , p2 : found . p2 , p3 : pipelineId . toLowerCase ( ) , service : found . service , sourceRepo : found . sourceRepo , } ; if ( manifestCommitId ) { entry . manifestCommitId = manifestCommitId . toLowerCase ( ) ; } if ( pr ) { entry . pr = pr ; } if ( repository ) { entry . manifestRepo = repository . toLowerCase ( ) ; } await updateEntryInTable ( tableInfo , entry ) ; logger . info ( "Updated third pipeline details for its corresponding pipeline" ) ; return entry ; } return null ; } ; param tableInfo param hldCommitId param pipelineId param manifestCommitId param pr export const addNewRowToHLDtoManifestPipeline = async ( tableInfo , hldCommitId , pipelineId , manifestCommitId ? , pr ? , repository ? , similarEntry ? ) : Promise < DeploymentEntry > => { const newEntry = { PartitionKey : tableInfo . partitionKey , RowKey : getRowKey ( ) , commitId : similarEntry ?. commitId ? similarEntry . commitId : "" , env : similarEntry ?. env ? similarEntry . env : "" , hldCommitId : hldCommitId . toLowerCase ( ) , hldRepo : similarEntry ?. hldRepo ? similarEntry . hldRepo : "" , imageTag : similarEntry ?. imageTag ? similarEntry . imageTag : "" , p1 : similarEntry ?. p1 ? similarEntry . p1 : "" , p2 : similarEntry ?. p2 ? similarEntry . p2 : "" , p3 : pipelineId . toLowerCase ( ) , service : similarEntry ?. service ? similarEntry . service : "" , sourceRepo : similarEntry ?. sourceRepo ? similarEntry . sourceRepo : "" , } ; if ( manifestCommitId ) { newEntry . manifestCommitId = manifestCommitId . toLowerCase ( ) ; } if ( pr ) { newEntry . pr = pr . toLowerCase ( ) ; } if ( repository ) { newEntry . manifestRepo = repository . toLowerCase ( ) ; } await insertToTable ( tableInfo , newEntry ) ; logger . info ( `Added new p3 entry for hldCommitId ${ hldCommitId } - ${ similarEntry ? "by finding a similar entry" : "no matching entry was found." }` ) ; return newEntry ; } ; param entries param tableInfo param hldCommitId param pipelineId param manifestCommitId param pr export const updateHLDtoManifestHelper = async ( entries , tableInfo , hldCommitId , pipelineId , manifestCommitId ? , pr ? , repository ? ) : Promise < DeploymentEntry > => { if ( entries && entries . length > 0 ) { const updated = await updateHLDtoManifestEntry ( entries , tableInfo , hldCommitId , pipelineId , manifestCommitId , pr , repository ) ; if ( updated ) { return updated ; } return await addNewRowToHLDtoManifestPipeline ( tableInfo , hldCommitId , pipelineId , manifestCommitId , pr , repository , entries [ entries . length - 1 ] ) ; } return await addNewRowToHLDtoManifestPipeline ( tableInfo , hldCommitId , pipelineId , manifestCommitId , pr , repository ) ; } ; param tableInfo param hldCommitId param pipelineId param manifestCommitId param pr export const updateHLDToManifestPipeline = async ( tableInfo , hldCommitId , pipelineId , manifestCommitId ? , pr ? , repository ? ) : Promise < DeploymentEntry > => { try { let entries = await findMatchingDeployments ( tableInfo , "hldCommitId" , hldCommitId ) ; if ( ( ! entries || entries . length === 0 ) && pr ) { entries = await findMatchingDeployments ( tableInfo , "pr" , pr ) ; } return updateHLDtoManifestHelper ( entries , tableInfo , hldCommitId , pipelineId , manifestCommitId , pr , repository ) ; } catch ( err ) { throw buildError ( errorStatusCode . AZURE_STORAGE_OP_ERR , "deployment-table-update-hld-manifest-pipeline-failed" , err ) ; } } ; param tableInfo param pipelineId param manifestCommitId export const updateManifestCommitId = async ( tableInfo , pipelineId , manifestCommitId , repository ? ) : Promise < DeploymentEntry > => { try { const entries = await findMatchingDeployments ( tableInfo , "p3" , pipelineId ) ; if ( entries . length > 0 ) { const entry = entries [ 0 ] ; entry . manifestCommitId = manifestCommitId ; if ( repository ) { entry . manifestRepo = repository . toLowerCase ( ) ; } await updateEntryInTable ( tableInfo , entry ) ; logger . info ( `Update manifest commit Id ${ manifestCommitId } for pipeline Id ${ pipelineId }` ) ; return entry ; } } catch ( err ) { throw buildError ( errorStatusCode . AZURE_STORAGE_OP_ERR , "deployment-table-update-manifest-commit-id-failed" , err ) ; } throw buildError ( errorStatusCode . AZURE_STORAGE_OP_ERR , { errorKey : "deployment-table-update-manifest-commit-id-failed-no-generation" , values : [ manifestCommitId ] , } ) ; } ;