"use strict" ; import * as azdev from "azure-devops-node-api" ; import { IHeaders , IRequestHandler } from "azure-devops-node-api/interfaces/common/VsoBaseInterfaces" ; import util from "util" ; import * as CoreInterfaces from 'azure-devops-node-api/interfaces/CoreInterfaces' ; import { GitRefUpdate , GitCommitRef , GitPush , GitChange , VersionControlChangeType , GitItem , ItemContentType , GitRef , GitImportRequest , GitRepositoryCreateOptions , GitImportRequestParameters , GitImportGitSource , GitAsyncOperationStatus } from 'azure-devops-node-api/interfaces/GitInterfaces' ; import * as gitm from "azure-devops-node-api/GitApi" ; import * as BuildInterfaces from 'azure-devops-node-api/interfaces/BuildInterfaces' ; import axios from 'axios' ; import open = require ( 'open' ) ; import { GitRepository , ItemContent } from "azure-devops-node-api/interfaces/TfvcInterfaces" ; import { IBuildApi } from "azure-devops-node-api/BuildApi" ; import { PolicyConfiguration } from "azure-devops-node-api/interfaces/PolicyInterfaces" ; import { execSync , ExecSyncOptionsWithStringEncoding } from 'child_process' ; import path = require ( 'path' ) ; import * as fs from 'fs' ; import { FileHandle } from 'fs/promises' ; import httpm = require ( 'typed-rest-client/HttpClient' ) ; import { AADAppInstallArguments , AADCommand } from "./aad" ; import { EndpointAuthorization , ServiceEndpoint , TaskAgentPool , VariableGroupParameters } from "azure-devops-node-api/interfaces/TaskAgentInterfaces" ; import { ProjectReference , VariableGroupProjectReference , VariableValue } from "azure-devops-node-api/interfaces/ReleaseInterfaces" ; import * as winston from 'winston' ; import { Environment } from "../common/environment" ; import { Prompt } from "../common/prompt" ; import { InstalledExtension } from "azure-devops-node-api/interfaces/ExtensionManagementInterfaces" ; import url from 'url' ; import { RoleAssignment } from "azure-devops-node-api/interfaces/SecurityRolesInterfaces" ; /**
* Azure DevOps Commands
*/ class DevOpsCommand { createWebApi : ( orgUrl : string , authHandler : IRequestHandler ) => azdev . WebApi ; createAADCommand : ( ) => AADCommand ; getUrl : ( url : string ) => Promise < string > ; runCommand : ( command : string , displayOutput : boolean ) => string ; prompt : Prompt ; getHttpClient : ( connection : azdev . WebApi ) => httpm . HttpClient ; logger : winston . Logger ; readFile : ( path : fs . PathLike | FileHandle , options : { encoding : BufferEncoding ; flag ? : fs . OpenMode ; } | BufferEncoding ) => Promise < string > ; constructor ( logger , defaultFs = null ) { this . logger = logger ; this . createWebApi = ( orgUrl , authHandler ) => new azdev . WebApi ( orgUrl , authHandler ) ; this . createAADCommand = ( ) => new AADCommand ( this . logger ) ; this . getUrl = async ( url ) => { return ( await ( axios . get < string > ( url ) ) ) . data ; } ; this . runCommand = ( command , displayOutput ) => { if ( displayOutput ) { return execSync ( command , < ExecSyncOptionsWithStringEncoding > { stdio : 'inherit' , encoding : 'utf8' } ) ; } else { return execSync ( command , < ExecSyncOptionsWithStringEncoding > { encoding : 'utf8' } ) ; } } ; this . prompt = new Prompt ( ) ; this . getHttpClient = ( connection ) => connection . rest . client ; if ( defaultFs == null ) { this . readFile = fs . promises . readFile ; } else { this . readFile = defaultFs . readFile ; } } param args returns async install ( args ) { let extensions = path . join ( __dirname , '..' , '..' , '..' , 'config' , 'AzureDevOpsExtensionsDetails.json' ) ; let orgUrl = Environment . getDevOpsOrgUrl ( args ) ; if ( args . extensions . length == 0 ) { this . logger ?. info ( 'Loading DevOps Extensions Configuration' ) ; let extensionConfig = JSON . parse ( await this . readFile ( extensions , 'utf-8' ) ) ; for ( let i = 0 ; i < extensionConfig . length ; i ++ ) { args . extensions . push ( < DevOpsExtension > { name : extensionConfig [ i ] . extensionId , publisher : extensionConfig [ i ] . publisherId } ) ; } } let authHandler = azdev . getBearerHandler ( typeof args . accessTokens [ "499b84ac-1321-427f-aa17-267ca6975798" ] !== "undefined" ? args . accessTokens [ "499b84ac-1321-427f-aa17-267ca6975798" ] : args . accessToken ) ; let connection = this . createWebApi ( orgUrl , authHandler ) ; await this . installExtensions ( args , connection ) ; let repo = await this . importPipelineRepository ( args , connection ) ; if ( repo !== null ) { await this . createMakersBuildPipelines ( args , connection , repo ) ; let securityContext = await this . setupSecurity ( args , connection ) ; await this . createMakersBuildVariables ( args , connection , securityContext ) ; await this . createMakersServiceConnections ( args , connection ) ; } } async setupSecurity ( args , connection ) { let context = < DevOpsProjectSecurityContext > { } ; let coreApi = await connection . getCoreApi ( ) ; let projects = await coreApi . getProjects ( ) ; let project = projects . filter ( p => p . name ?. toLowerCase ( ) == args . projectName ?. toLowerCase ( ) ) ; if ( project . length != 1 ) { return Promise . resolve ( context ) ; } let client = this . getHttpClient ( connection ) ; let devOpsOrgUrl = Environment . getDevOpsOrgUrl ( args ) ; context . projectId = project [ 0 ] . id ; context . securityUrl = devOpsOrgUrl . replace ( "https://dev" , "https://vssps.dev" ) ; this . logger ?. debug ( `Getting descriptor for project ${ project [ 0 ] . id }` ) ; context . projectDescriptor = await this . getSecurityDescriptor ( client , context . securityUrl , project [ 0 ] . id ) ; let headers = < IHeaders > { } ; headers [ "Content-Type" ] = "application/json" ; this . logger ?. debug ( `Getting groups for project ${ args . projectName } (${ context . projectDescriptor })` ) ; let query = await client . get ( `${ context . securityUrl }_apis/Graph/Groups?scopeDescriptor=${ context . projectDescriptor }&api-version=6.0-preview.1` ) ; let groupJson = await query . readBody ( ) ; let groups = JSON . parse ( groupJson ) ; let groupMatch = groups . value . filter ( ( g ) => g . displayName == "ALM Accelerator for Makers" ) ; let almGroup ; if ( groupMatch . length == 0 ) { let newGroup = { "displayName" : "ALM Accelerator for Makers" , "description" : "Members of this group will be able to access resources required for operation of the ALM Accelerator for Makers" , "storageKey" : "" , "crossProject" : false , "descriptor" : "" , "restrictedVisibility" : false , "specialGroupType" : "Generic" } ; this . logger ?. info ( `Creating new Group ${ newGroup . displayName }` ) ; let create = await client . post ( `${ context . securityUrl }_apis/graph/groups?scopeDescriptor=${ context . projectDescriptor }&api-version=6.0-preview.1` , JSON . stringify ( newGroup ) , headers ) ; let createdJson = await create . readBody ( ) ; almGroup = JSON . parse ( createdJson ) ; } else { almGroup = groupMatch [ 0 ] ; } context . almGroup = almGroup ; let makerAADGroup = await this . getSecurityAADUserGroup ( client , context . securityUrl , args . azureActiveDirectoryMakersGroup , almGroup . descriptor ) ; if ( makerAADGroup != null ) { this . logger ?. debug ( `Getting members for ALM Accelerator for Makers (${ almGroup . originId })` ) ; let members = await this . getGroupMembers ( client , context . securityUrl , almGroup . descriptor ) ; let match = members ?. value ?. filter ( ( m ) => m . memberDescriptor == makerAADGroup ) ; if ( match ?. length == 1 ) { this . logger ?. info ( "Group already a member of group" ) ; } else { this . logger ?. info ( "Adding member to group" ) ; let update = await client . put ( `${ context . securityUrl }_apis/Graph/Memberships/${ makerAADGroup }/${ almGroup . descriptor }?api-version=5.2-preview.1` , "" , headers ) ; let updateData = await update . readBody ( ) ; this . logger ?. debug ( updateData ) ; } } return context ; } async getSecurityAADUserGroup ( client , url , name , groupDescriptor ) { let headers = < IHeaders > { } ; headers [ "Content-Type" ] = "application/json" ; let request = { "query" : name , "identityTypes" : [ "user" , "group" ] , "operationScopes" : [ "ims" , "source" ] , "options" : { "MinResults" : 5 , "MaxResults" : 20 } , "properties" : [ "DisplayName" , "SubjectDescriptor" ] } ; let descriptor = await client . post ( `${ url }_apis/IdentityPicker/Identities?api-version=6.0-preview.1` , JSON . stringify ( request ) , headers ) ; let descriptorJson = await descriptor . readBody ( ) ; let descriptorInfo = JSON . parse ( descriptorJson ) ; let aadGroupFilter = ( i ) => i . displayName == name && i . entityType == "Group" && i . originDirectory == 'aad' ; let devOpsAADGroupFilter = ( i ) => i . displayName == `[TEAM FOUNDATION]\\${ name }` && i . entityType == "Group" && i . originDirectory == 'aad' ; let match = descriptorInfo . results ?. filter ( ( r ) => r . identities ?. filter ( ( i ) => aadGroupFilter ( i ) || devOpsAADGroupFilter ( i ) ) . length == 1 ) ; if ( match . length == 1 ) { let identityMatch = match [ 0 ] . identities ?. filter ( ( i ) => aadGroupFilter ( i ) || devOpsAADGroupFilter ( i ) ) [ 0 ] ; if ( identityMatch . subjectDescriptor == null ) { this . logger ?. info ( `Adding Azure Active Directory Group (${ identityMatch . originId }) to DevOps` ) ; let addToDevOps = await client . post ( `${ url }_apis/Graph/Groups?groupDescriptors=${ groupDescriptor }&api-version=5.2-preview.1` , JSON . stringify ( { "originId" : identityMatch . originId , "storageKey" : "" } ) , headers ) ; let resultJson = await addToDevOps . readBody ( ) ; let addToDevOpsResult = JSON . parse ( resultJson ) ; return addToDevOpsResult . descriptor ; } else { return identityMatch . subjectDescriptor ; } } if ( match . length == 0 ) { this . logger . info ( `No match found for Azure Active Directory Group ${ name }` ) ; return null ; } this . logger . info ( `Multiple matches Azure Active Directory Group ${ name }` ) ; for ( let i = 0 ; i < descriptorInfo ?. results ?. identities ?. length ; i ++ ) { this . logger . info ( descriptorInfo ?. results ?. identities [ i ] . displayName ) ; } return null ; } async getSecurityAADUserGroupReference ( client , url , name , groupDescriptor ) { let headers = < IHeaders > { } ; headers [ "Content-Type" ] = "application/json" ; let request = { "query" : name , "identityTypes" : [ "user" , "group" ] , "operationScopes" : [ "ims" , "source" ] , "options" : { "MinResults" : 5 , "MaxResults" : 20 } , "properties" : [ "DisplayName" , "SubjectDescriptor" ] } ; let descriptor = await client . post ( `${ url }_apis/IdentityPicker/Identities?api-version=6.0-preview.1` , JSON . stringify ( request ) , headers ) ; let descriptorJson = await descriptor . readBody ( ) ; let descriptorInfo = JSON . parse ( descriptorJson ) ; let aadGroupFilter = ( i ) => i . displayName == name && i . entityType == "Group" && i . originDirectory == 'aad' ; let devOpsAADGroupFilter = ( i ) => i . displayName == `[TEAM FOUNDATION]\\${ name }` && i . entityType == "Group" && i . originDirectory == 'aad' ; let match = descriptorInfo . results ?. filter ( ( r ) => r . identities ?. filter ( ( i ) => aadGroupFilter ( i ) || devOpsAADGroupFilter ( i ) ) . length == 1 ) ; if ( match . length == 1 ) { let identityMatch = match [ 0 ] . identities ?. filter ( ( i ) => aadGroupFilter ( i ) || devOpsAADGroupFilter ( i ) ) [ 0 ] ; if ( identityMatch . subjectDescriptor == null ) { this . logger ?. info ( `Adding Azure Active Directory Group (${ identityMatch . originId }) to DevOps` ) ; let addToDevOps = await client . post ( `${ url }_apis/Graph/Groups?groupDescriptors=${ groupDescriptor }&api-version=5.2-preview.1` , JSON . stringify ( { "originId" : identityMatch . originId , "storageKey" : "" } ) , headers ) ; let resultJson = await addToDevOps . readBody ( ) ; let addToDevOpsResult = JSON . parse ( resultJson ) ; return addToDevOpsResult . descriptor ; } else { return identityMatch . subjectDescriptor ; } } if ( match . length == 0 ) { this . logger . info ( `No match found for Azure Active Directory Group ${ name }` ) ; return null ; } this . logger . info ( `Multiple matches Azure Active Directory Group ${ name }` ) ; for ( let i = 0 ; i < descriptorInfo ?. results ?. identities ?. length ; i ++ ) { this . logger . info ( descriptorInfo ?. results ?. identities [ i ] . displayName ) ; } return null ; } async getSecurityDescriptor ( client , url , id ) { let descriptor = await client . get ( `${ url }_apis/graph/descriptors/${ id }?api-version=6.0-preview.1` ) ; let descriptorJson = await descriptor . readBody ( ) ; let descriptorInfo = JSON . parse ( descriptorJson ) ; return descriptorInfo . value ; } async getGroupMembers ( client , url , id ) { let results = await client . get ( `${ url }_apis/graph/Memberships/${ id }?direction=Down&api-version=6.0-preview.1` ) ; let resultsJson = await results . readBody ( ) ; return JSON . parse ( resultsJson ) ; } async installExtensions ( args , connection ) { if ( args . extensions . length == 0 ) { return Promise . resolve ( ) ; } this . logger . info ( `Checking DevOps Extensions` ) ; let extensionsApi = await connection . getExtensionManagementApi ( ) ; let extensions = await extensionsApi . getInstalledExtensions ( ) ; for ( let i = 0 ; i < args . extensions . length ; i ++ ) { let extension = args . extensions [ i ] ; let match = extensions . filter ( ( e ) => e . extensionId == extension . name && e . publisherId == extension . publisher ) ; if ( match . length == 0 ) { this . logger . info ( `Installing ${ extension . name } by ${ extension . publisher }` ) ; await extensionsApi . installExtensionByName ( extension . publisher , extension . name ) ; } else { this . logger . info ( `Extension ${ extension . name } by ${ extension . publisher } installed` ) ; } } } async importPipelineRepository ( args , connection ) { let gitApi = await connection . getGitApi ( ) ; this . logger . info ( `Checking pipeline repository` ) ; let repo = await this . getRepository ( args , gitApi , args . pipelineRepositoryName ) ; if ( repo == null ) { return Promise . resolve ( null ) ; } let refs = await gitApi . getRefs ( repo . id , args . projectName ) ; if ( refs . length == 0 ) { this . logger ?. debug ( `Importing ${ args . repositoryName }` ) ; repo . defaultBranch = "refs/heads/main" ; let importRequest = await gitApi . createImportRequest ( < GitImportRequest > { parameters : < GitImportRequestParameters > { gitSource : < GitImportGitSource > { url : "https://github.com/microsoft/coe-alm-accelerator-templates.git" } } , repository : repo } , args . projectName , repo . id ) ; while ( true ) { let requests = await gitApi . queryImportRequests ( args . projectName , repo . id ) ; let current = requests . filter ( r => r . importRequestId == importRequest . importRequestId ) [ 0 ] ; if ( current . status == GitAsyncOperationStatus . Completed || current . status == GitAsyncOperationStatus . Abandoned || current . status == GitAsyncOperationStatus . Failed ) { break ; } await this . sleep ( 500 ) ; } this . logger ?. debug ( 'Setting default branch' ) ; let headers = < IHeaders > { } ; headers [ "Content-Type" ] = "application/json" ; let devOpsOrgUrl = Environment . getDevOpsOrgUrl ( args ) ; await this . getHttpClient ( connection ) . patch ( `${ devOpsOrgUrl }${ args . projectName }/_apis/git/repositories/${ repo . id }?api-version=6.0` , '{"defaultBranch":"refs/heads/main"}' , headers ) ; } else { this . logger . info ( `Pipeline repository ${ args . repositoryName }` ) ; } return repo ; } private async getRepository ( args , gitApi , repositoryName ) { let repos = await gitApi . getRepositories ( args . projectName ) ; if ( repos == null ) { this . logger ?. error ( `${ args . projectName } not found` ) ; return Promise . resolve ( null ) ; } if ( repos ?. filter ( r => r . name == repositoryName ) . length == 0 ) { this . logger ?. debug ( `Creating repository ${ args . repositoryName }` ) ; return await gitApi . createRepository ( < GitRepositoryCreateOptions > { name : repositoryName } , args . projectName ) ; } else { return repos . filter ( r => r . name == args . repositoryName ) [ 0 ] ; } } sleep ( ms ) { return new Promise ( resolve => setTimeout ( resolve , ms ) ) ; } param args param connection param repo async createMakersBuildPipelines ( args , connection , repo ) { connection = await this . createConnectionIfExists ( args , connection ) ; if ( repo == null ) { let gitApi = await connection . getGitApi ( ) ; repo = await this . getRepository ( args , gitApi , args . repositoryName ) ; } let buildApi = await connection . getBuildApi ( ) ; if ( typeof buildApi == "undefined" ) { this . logger ?. info ( "Build API missing" ) ; return ; } let taskApi = await connection . getTaskAgentApi ( ) ; let defaultPool = ( await taskApi ?. getAgentPools ( ) ) ?. filter ( p => p . name == "Default" ) ; let defaultAgentPool = defaultPool ?. length > 0 ? defaultPool [ 0 ] : undefined ; let builds = await buildApi . getDefinitions ( args . projectName ) ; let buildNames = [ 'export-solution-to-git' , 'import-unmanaged-to-dev-environment' , 'delete-unmanaged-solution-and-components' ] ; for ( var i = 0 ; i < buildNames . length ; i ++ ) { let exportBuild = builds . filter ( b => b . name == buildNames [ i ] ) ; if ( exportBuild . length == 0 ) { this . logger ?. debug ( `Creating build ${ buildNames [ i ] }` ) ; await this . createBuild ( buildApi , repo , buildNames [ i ] , `/Pipelines/${ buildNames [ i ] }.yml` , defaultAgentPool ) ; } else { let build = await buildApi . getDefinition ( args . projectName , exportBuild [ 0 ] . id ) ; let changes = false ; if ( typeof build . queue === "undefined" ) { this . logger ?. debug ( `Missing build queue for ${ build . name }` ) ; build . queue = < BuildInterfaces . BuildDefinitionReference > { queue : defaultAgentPool } ; changes = true ; } if ( changes ) { this . logger ?. debug ( `Updating ${ build . name }` ) ; await buildApi . updateDefinition ( build , args . projectName , exportBuild [ 0 ] . id ) ; } else { this . logger ?. debug ( `No changes to ${ buildNames [ i ] }` ) ; } } } } async createMakersBuildVariables ( args , connection , securityContext ) { connection = await this . createConnectionIfExists ( args , connection ) ; let taskApi = await connection . getTaskAgentApi ( ) ; let groups = await taskApi ?. getVariableGroups ( args . projectName ) ; let variableGroupName = "alm-accelerator-variable-group" ; let global = groups ?. filter ( g => g . name == variableGroupName ) ; let variableGroup = global ?. length == 1 ? global [ 0 ] : null ; if ( global ?. length == 0 ) { let aadCommand = this . createAADCommand ( ) ; let aadArgs = new AADAppInstallArguments ( ) ; aadArgs . subscription = args . subscription ; aadArgs . azureActiveDirectoryServicePrincipal = args . azureActiveDirectoryServicePrincipal ; aadArgs . createSecret = args . createSecretIfNoExist ; aadArgs . accessTokens = args . accessTokens ; aadArgs . endpoint = args . endpoint ; let secretInfo = await aadCommand . addSecret ( aadArgs , "CoE-ALM" ) ; if ( ! aadArgs . createSecret ) { this . logger ?. warn ( 'Client secret not added for variable group alm-accelerator-variable-group it wil need to be added manually' ) ; } let buildApi = await connection . getBuildApi ( ) ; let builds = await buildApi . getDefinitions ( args . projectName ) ; let exportBuild = builds . filter ( b => b . name == "export-solution-to-git" ) ; let buildId = exportBuild . length == 1 ? exportBuild [ 0 ] . id . toString ( ) : "" ; let paramemeters = < VariableGroupParameters > { } ; paramemeters . variableGroupProjectReferences = [ < VariableGroupProjectReference > { name : variableGroupName , projectReference : < ProjectReference > { name : args . projectName , } } ] ; paramemeters . name = variableGroupName ; paramemeters . description = 'ALM Accelerator for Makers' ; paramemeters . variables = { "CdsBaseConnectionString" : < VariableValue > { value : "AuthType=ClientSecret;ClientId=$(ClientId);ClientSecret=$(ClientSecret);Url=" } , "ClientId" : < VariableValue > { value : secretInfo . clientId } , "ClientSecret" : < VariableValue > { isSecret : true , value : secretInfo . clientSecret } , "TenantID" : < VariableValue > { value : secretInfo . tenantId } } ; variableGroup = await taskApi . addVariableGroup ( paramemeters ) ; } this . logger ?. debug ( "Searching for existing role assignements" ) ; let variableGroupId = `${ securityContext . projectId }%24${ variableGroup . id }` ; let client = this . getHttpClient ( connection ) ; let devOpsOrgUrl = Environment . getDevOpsOrgUrl ( args ) ; let variableGroupUrl = `${ devOpsOrgUrl }_apis/securityroles/scopes/distributedtask.variablegroup/roleassignments/resources/${ variableGroupId }?api-version=6.1-preview.1` ; let roleRequest = await client . get ( variableGroupUrl ) ; let roleJson = await roleRequest . readBody ( ) ; let roleAssignmentsResponse = JSON . parse ( roleJson ) ; let roleAssignments = < RoleAssignment [ ] > roleAssignmentsResponse . value ; if ( roleAssignments . filter ( ( r ) => r . identity . id == securityContext . almGroup . originId ) . length == 0 ) { this . logger ?. debug ( `Adding User role for Group ${ securityContext . almGroup . displayName }` ) ; let headers = < IHeaders > { } ; headers [ "Content-Type" ] = "application/json" ; let updateRequest = await client . put ( variableGroupUrl , JSON . stringify ( [ { "roleName" : "User" , "userId" : securityContext . almGroup . originId } ] ) , headers ) ; let newRoleAssignmentJson = await updateRequest . readBody ( ) ; let newRoleAssignmentResult = JSON . parse ( newRoleAssignmentJson ) ; if ( newRoleAssignmentResult . value ?. length == 1 ) { let newRoleAssignment = < RoleAssignment > newRoleAssignmentResult . value [ 0 ] ; this . logger ?. info ( `Added new role assignnment ${ newRoleAssignment . identity . displayName } for variable group ${ variableGroupName }` ) ; } else { this . logger ?. error ( `Role for ${ securityContext . almGroup . displayName } not assigned to ${ variableGroupName }` ) ; } } } async createMakersServiceConnections ( args , connection , setupEnvironmentConnections = true ) { connection = await this . createConnectionIfExists ( args , connection ) ; let endpoints = await this . getServiceConnections ( args , connection ) ; let coreApi = await connection . getCoreApi ( ) ; let projects = await coreApi . getProjects ( ) ; let project = projects . filter ( p => p . name ?. toLowerCase ( ) == args . projectName ?. toLowerCase ( ) ) ; if ( project . length == 0 ) { this . logger ?. error ( `Azure DevOps project ${ args . projectName } not found` ) ; return Promise . resolve ( ) ; } let aadCommand = this . createAADCommand ( ) ; let aadArgs = new AADAppInstallArguments ( ) ; aadArgs . subscription = args . subscription ; aadArgs . azureActiveDirectoryServicePrincipal = args . azureActiveDirectoryServicePrincipal ; aadArgs . createSecret = args . createSecretIfNoExist ; aadArgs . accessTokens = args . accessTokens ; aadArgs . endpoint = args . endpoint ; let keys = Object . keys ( args . environments ) ; let environments = [ ] ; if ( args . environment ?. length > 0 ) { environments . push ( args . environment ) ; } let mapping = { } ; if ( setupEnvironmentConnections ) { for ( var i = 0 ; i < keys . length ; i ++ ) { let environmentName = args . environments [ keys [ i ] ] ; mapping [ environmentName ] = keys [ i ] ; if ( environments . filter ( ( e ) => e == environmentName ) . length == 0 ) { environments . push ( environmentName ) ; } } if ( Array . isArray ( args . settings [ "installEnvironments" ] ) ) { for ( var i = 0 ; i < args . settings [ "installEnvironments" ] . length ; i ++ ) { let environmentName = args . settings [ "installEnvironments" ] [ i ] ; if ( typeof args . settings [ environmentName ] === "string" && environments . filter ( ( e ) => e == args . settings [ environmentName ] ) . length == 0 ) { environments . push ( args . settings [ environmentName ] ) ; } } } } for ( var i = 0 ; i < environments . length ; i ++ ) { let environmentName = environments [ i ] ; let endpointUrl = Environment . getEnvironmentUrl ( environmentName , args . settings ) ; let secretName = environmentName ; try { let environmentUrl = new url . URL ( secretName ) ; secretName = environmentUrl . hostname . split ( "." ) [ 0 ] ; } catch { } let secretInfo = await aadCommand . addSecret ( aadArgs , secretName ) ; if ( endpoints . filter ( e => e . name == endpointUrl ) . length == 0 ) { let ep = < ServiceEndpoint > { authorization : < EndpointAuthorization > { parameters : { tenantId : secretInfo . tenantId , clientSecret : secretInfo . clientSecret , applicationId : secretInfo . clientId } , scheme : "None" } , name : endpointUrl , type : "powerplatform-spn" , url : endpointUrl , description : typeof mapping [ environmentName ] !== "undefined" ? `Environment ${ mapping [ environmentName ] }` : '' , serviceEndpointProjectReferences : [ { projectReference : < ProjectReference > { id : project [ 0 ] . id , name : args . projectName } , name : endpointUrl } ] } ; let headers = < IHeaders > { } ; headers [ "Content-Type" ] = "application/json" ; let webClient = this . getHttpClient ( connection ) ; let devOpsOrgUrl = Environment . getDevOpsOrgUrl ( args ) ; let create = await webClient . post ( `${ devOpsOrgUrl }${ args . projectName }/_apis/serviceendpoint/endpoints?api-version=6.0-preview.4` , JSON . stringify ( ep ) , headers ) ; let serviceConnection ; serviceConnection = JSON . parse ( await create . readBody ( ) ) ; if ( create . message . statusCode != 200 ) { return Promise . resolve ( ) ; } else { this . logger ?. info ( `Created service connection ${ endpointUrl }` ) ; } await this . assignUserToServiceConnector ( project [ 0 ] , serviceConnection , args , connection ) ; } else { await this . assignUserToServiceConnector ( project [ 0 ] , endpointUrl , args , connection ) ; } } } async assignUserToServiceConnector ( project , endpoint , args , connection ) { if ( args . user ?. length > 0 ) { let webClient = this . getHttpClient ( connection ) ; let devOpsOrgUrl = Environment . getDevOpsOrgUrl ( args ) ; if ( typeof endpoint === "string" ) { let results = await webClient . get ( `${ devOpsOrgUrl }${ args . projectName }/_apis/serviceendpoint/endpoints?api-version=6.0-preview.4` ) ; let endpointJson = await results . readBody ( ) ; this . logger ?. debug ( endpointJson ) ; let endpoints = < any [ ] > ( JSON . parse ( endpointJson ) . value ) ; this . logger ?. debug ( endpoints ) ; let endPointMatch = endpoints . filter ( ( ep ) => ep . url == endpoint ) ; if ( endPointMatch . length == 1 ) { endpoint = endPointMatch [ 0 ] ; } else { this . logger . error ( `Unable to find service connection ${ endpoint }` ) ; return Promise . resolve ( ) ; } } let userId = await this . getUserId ( devOpsOrgUrl , args . user , connection ) ; if ( userId == null ) { this . logger ?. info ( "No user found -- Exiting" ) ; return Promise . resolve ( ) ; } else { this . logger ?. info ( `Found user ${ userId }` ) ; } let connectorRoles = await webClient . get ( `${ devOpsOrgUrl }_apis/securityroles/scopes/distributedtask.serviceendpointrole/roleassignments/resources/${ project . id }_${ endpoint . id }` ) ; let connectorData = JSON . parse ( await connectorRoles . readBody ( ) ) ; let connectorMatch = connectorData . value ?. filter ( ( c ) => c . identity . id == userId ) ; if ( connectorMatch ?. length == 0 ) { let headers = < IHeaders > { } ; headers [ "Content-Type" ] = "application/json" ; let newRole = [ { "roleName" : "User" , "userId" : userId } ] ; this . logger ?. info ( `Assigning user ${ args . user } to service connection ${ endpoint . url }` ) ; let update = await webClient . put ( `${ devOpsOrgUrl }_apis/securityroles/scopes/distributedtask.serviceendpointrole/roleassignments/resources/${ project . id }_${ endpoint . id }?api-version=6.1-preview.1` , JSON . stringify ( newRole ) , headers ) ; if ( update . message . statusCode != 200 ) { this . logger ?. info ( "Update failed" ) ; this . logger ?. error ( await update . readBody ( ) ) ; } else { this . logger ?. info ( 'User role assigned' ) ; let results = await update . readBody ( ) ; this . logger ?. debug ( results ) ; } } else { this . logger ?. info ( "User role already assigned" ) ; } } } async getUserId ( devOpsOrgUrl , user , connection ) { let client = this . getHttpClient ( connection ) ; let query = await client . get ( `${ devOpsOrgUrl . replace ( "https://dev" , "https://vssps.dev" ) }_apis/identities?searchFilter=General&filterValue=${ user }&queryMembership=None&api-version=6.0` ) ; let identityJson = await query . readBody ( ) ; let users = JSON . parse ( identityJson ) ; this . logger ?. debug ( `Found ${ users . value ?. length } user(s)` ) ; this . logger ?. verbose ( users ) ; this . logger ?. debug ( `Searching for ${ user }` ) ; let userMatch = users . value ?. filter ( ( u ) => u . properties ?. Account [ '$value' ] ?. toLowerCase ( ) == user ?. toLowerCase ( ) ) ; if ( userMatch ?. length == 1 ) { this . logger ?. debug ( `Found user ${ userMatch [ 0 ] . id }` ) ; return userMatch [ 0 ] . id ; } if ( userMatch ?. length == 0 ) { this . logger ?. error ( `Unable to find ${ user } in ${ devOpsOrgUrl }, has the used been added?` ) ; return null ; } if ( userMatch ?. length > 1 ) { this . logger ?. error ( `More than one match for ${ user } in ${ devOpsOrgUrl }` ) ; return null ; } } param connection returns async getServiceConnections ( args , connection ) { let webClient = this . getHttpClient ( connection ) ; let devOpsOrgUrl = Environment . getDevOpsOrgUrl ( args , args . settings ) ; let request = await webClient . get ( `${ devOpsOrgUrl }${ args . projectName }/_apis/serviceendpoint/endpoints?api-version=6.0-preview.4` ) ; let data = await request . readBody ( ) ; this . logger ?. debug ( data ) ; return < ServiceEndpoint [ ] > ( JSON . parse ( data ) . value ) ; } private async createConnectionIfExists ( args , connection ) { if ( connection == null ) { let authHandler = azdev . getBearerHandler ( args . accessToken ?. length > 0 ? args . accessToken : args . accessTokens [ "499b84ac-1321-427f-aa17-267ca6975798" ] , true ) ; let devOpsOrgUrl = Environment . getDevOpsOrgUrl ( args , args . settings ) ; return this . createWebApi ( devOpsOrgUrl , authHandler ) ; } return connection ; } async createBuild ( buildApi , repo , name , yamlFilename , defaultPool ) { let newBuild = < BuildInterfaces . BuildDefinition > { } ; newBuild . name = name ; newBuild . repository = < BuildInterfaces . BuildRepository > { } ; newBuild . repository . defaultBranch = "refs/heads/main" ; newBuild . repository . id = repo . id ; newBuild . repository . name = repo . name ; newBuild . repository . url = repo . url ; newBuild . repository . type = 'TfsGit' ; let process = < BuildInterfaces . YamlProcess > { } ; process . yamlFilename = yamlFilename ; newBuild . process = process ; newBuild . queue = < BuildInterfaces . BuildDefinitionReference > { queue : defaultPool } ; return buildApi . createDefinition ( newBuild , repo . project . name ) ; } param args { DevOpsBranchArguments } return { Promise } async branch ( args ) { let devOpsOrgUrl = Environment . getDevOpsOrgUrl ( args , args . settings ) ; let authHandler = azdev . getBearerHandler ( args . accessToken ) ; let connection = this . createWebApi ( devOpsOrgUrl , authHandler ) ; let core = await connection . getCoreApi ( ) ; let project = await core . getProject ( args . projectName ) ; if ( typeof project !== "undefined" ) { this . logger ?. info ( util . format ( "Found project %s" , project . name ) ) ; let gitApi = await connection . getGitApi ( ) ; let repo = await this . createBranch ( args , project , gitApi ) ; if ( repo != null ) { await this . createBuildForBranch ( args , project , repo , connection ) ; await this . setBranchPolicy ( args , repo , connection ) ; } } } param args param project param gitApi returns async createBranch ( args , project , gitApi ) { var repos = await gitApi . getRepositories ( project . id ) ; var matchingRepo ; let repositoryName = args . repositoryName ; if ( typeof repositoryName === "undefined" || repositoryName ?. length == 0 ) { repositoryName = args . projectName ; } let foundRepo = false ; for ( let i = 0 ; i < repos . length ; i ++ ) { let repo = repos [ i ] ; if ( repo . name . toLowerCase ( ) == repositoryName . toLowerCase ( ) ) { foundRepo = true ; matchingRepo = repo ; this . logger ?. debug ( `Found matching repo ${ repositoryName }` ) ; let refs = await gitApi . getRefs ( repo . id , undefined , "heads/" ) ; if ( refs . length == 0 ) { this . logger . error ( "No commits to this repository yet. Initialize this repository before creating new branches" ) ; return Promise . resolve ( null ) ; } let sourceBranch = args . sourceBranch ; if ( typeof sourceBranch === "undefined" || args . sourceBranch ?. length == 0 ) { sourceBranch = this . withoutRefsPrefix ( repo . defaultBranch ) ; } let sourceRef = refs . filter ( f => f . name == util . format ( "refs/heads/%s" , sourceBranch ) ) ; if ( sourceRef . length == 0 ) { this . logger ?. error ( util . format ( "Source branch [%s] not found" , sourceBranch ) ) ; this . logger ?. debug ( 'Existing branches' ) ; for ( var refIndex = 0 ; refIndex < refs . length ; refIndex ++ ) { this . logger ?. debug ( refs [ refIndex ] . name ) ; } return matchingRepo ; } let destinationRef = refs . filter ( f => f . name == util . format ( "refs/heads/%s" , args . destinationBranch ) ) ; if ( destinationRef . length > 0 ) { this . logger ?. error ( "Destination branch already exists" ) ; return matchingRepo ; } let newRef = < GitRefUpdate > { } ; newRef . repositoryId = repo . id ; newRef . oldObjectId = sourceRef [ 0 ] . objectId ; newRef . name = util . format ( "refs/heads/%s" , args . destinationBranch ) ; let newGitCommit = < GitCommitRef > { } ; newGitCommit . comment = "Add DevOps Pipeline" ; newGitCommit . changes = await this . getGitCommitChanges ( args . destinationBranch , this . withoutRefsPrefix ( repo . defaultBranch ) , args . pipelineRepository , [ 'validation' , 'test' , 'prod' ] ) ; let gitPush = < GitPush > { } ; gitPush . refUpdates = [ newRef ] ; gitPush . commits = [ newGitCommit ] ; this . logger ?. info ( util . format ( 'Pushing new branch %s' , args . destinationBranch ) ) ; await gitApi . createPush ( gitPush , repo . id , project . name ) ; } } if ( ! foundRepo && repositoryName ?. length > 0 ) { this . logger ?. info ( util . format ( "Repo %s not found" , repositoryName ) ) ; this . logger ?. info ( 'Did you mean?' ) ; repos . forEach ( repo => { if ( repo . name . startsWith ( repositoryName [ 0 ] ) ) { this . logger ?. info ( repo . name ) ; } } ) ; } return matchingRepo ; } param args param repo param connection async setBranchPolicy ( args , repo , connection ) { let policyApi = await connection . getPolicyApi ( ) ; if ( policyApi == null ) { return ; } let policyTypes = await policyApi . getPolicyTypes ( args . projectName ) ; let buildTypes = policyTypes . filter ( p => { if ( p . displayName == 'Build' ) { return true ; } } ) ; let buildApi = await connection . getBuildApi ( ) ; let builds = await buildApi . getDefinitions ( args . projectName ) ; let buildMatch = builds . filter ( b => { if ( b . name == `deploy-validation-${ args . destinationBranch }` ) { return true ; } } ) ; if ( buildTypes . length > 0 ) { let existingConfigurations = await policyApi . getPolicyConfigurations ( args . projectName ) ; let existingPolices = existingConfigurations . filter ( ( policy ) => { if ( policy . settings . scope ?. length == 1 && policy . settings . scope [ 0 ] . refName == `ref/heads/${ args . destinationBranch }` && policy . settings . scope [ 0 ] . repositorytId == repo . id && policy . type . id == policyTypes [ 0 ] . id ) { return true ; } } ) ; if ( ( existingPolices . length == 0 ) && ( buildMatch . length > 0 ) ) { let newPolicy = < PolicyConfiguration > { } ; newPolicy . settings = { } ; newPolicy . settings . buildDefinitionId = buildMatch [ 0 ] . id ; newPolicy . settings . displayName = 'Build Validation' ; newPolicy . settings . filenamePatterens = [ `/${ args . destinationBranch }/*` ] ; newPolicy . settings . manualQueueOnly = false ; newPolicy . settings . queueOnSourceUpdateOnly = false ; newPolicy . settings . validDuration = 0 ; let repoRef = { refName : `refs/heads/${ args . destinationBranch }` , matchKind : 'Exact' , repositoryId : repo . id } ; newPolicy . settings . scope = [ repoRef ] ; newPolicy . type = buildTypes [ 0 ] ; newPolicy . isBlocking = true ; newPolicy . isEnabled = true ; newPolicy . isEnterpriseManaged = false ; this . logger ?. info ( 'Checking branch policy' ) ; await policyApi . createPolicyConfiguration ( newPolicy , args . projectName ) ; } else { this . logger ?. info ( 'Branch policy already created' ) ; } } } withoutRefsPrefix ( refName ) { if ( ! refName . startsWith ( "refs/heads/" ) ) { throw Error ( "The ref name should have started with 'refs/heads/' but it didn't." ) ; } return refName . substr ( "refs/heads/" . length , refName . length - "refs/heads/" . length ) ; } param args param project param connection async createBuildForBranch ( args , project , repo , connection ) { let buildClient = await connection . getBuildApi ( ) ; let definitions = await buildClient . getDefinitions ( project . name ) ; let taskApi = await connection . getTaskAgentApi ( ) ; let defaultAgent = [ ] ; defaultAgent = ( await taskApi ?. getAgentPools ( ) ) ?. filter ( a => a . name == "Default" ) ; let devOpsOrgUrl = Environment . getDevOpsOrgUrl ( args , args . settings ) ; let baseUrl = `$(devOpsOrgUrl}${ args . projectName }` ; let defaultAgentPool = defaultAgent ?. length > 0 ? defaultAgent [ 0 ] : undefined ; if ( typeof args . settings [ "validation" ] === "undefined" ) { let taskApi = await connection . getTaskAgentApi ( ) ; let groups = await taskApi ?. getVariableGroups ( args . projectName , "alm-accelerator-variable-group" ) ; if ( groups ?. length == 1 ) { args . settings [ "validation" ] = groups [ 0 ] . variables [ "ValidationServiceConnection" ] ?. value ; } } await this . cloneBuildSettings ( definitions , buildClient , project , repo , baseUrl , args , "validation" , args . destinationBranch , false , defaultAgentPool ) ; await this . cloneBuildSettings ( definitions , buildClient , project , repo , baseUrl , args , "test" , args . destinationBranch , false , defaultAgentPool ) ; await this . cloneBuildSettings ( definitions , buildClient , project , repo , baseUrl , args , "prod" , args . destinationBranch , false , defaultAgentPool ) ; } async cloneBuildSettings ( pipelines , client , project , repo , baseUrl , args , template , createInBranch , required , defaultPool ) { let source = args . sourceBuildName ; let destination = args . destinationBranch ; var destinationBuildName = util . format ( "deploy-%s-%s" , template , destination ) ; var destinationBuilds = pipelines . filter ( p => p . name == destinationBuildName ) ; var sourceBuildName = util . format ( "deploy-%s-%s" , template , source ) ; var sourceBuilds = pipelines . filter ( p => p . name == sourceBuildName ) ; let destinationBuild = destinationBuilds . length > 0 ? await client . getDefinition ( destinationBuilds [ 0 ] . project . name , destinationBuilds [ 0 ] . id ) : null ; let sourceBuild = sourceBuilds . length > 0 ? await client . getDefinition ( sourceBuilds [ 0 ] . project ?. name , sourceBuilds [ 0 ] . id ) : null ; if ( destinationBuild != null && sourceBuild != null ) { let destinationKeys = Object . keys ( destinationBuild . variables ) ; let sourceKeys = Object . keys ( sourceBuild . variables ) ; if ( destinationKeys . length == 0 && sourceKeys . length > 0 ) { destinationBuild . variables = sourceBuild . variables ; this . logger ?. debug ( util . format ( "Updating %s environment variables" , destinationBuildName ) ) ; await client . updateDefinition ( destinationBuild , destinationBuild . project . name , destinationBuild . id ) ; return ; } } if ( destinationBuild != null ) { return ; } let defaultSettings = false ; if ( sourceBuild == null ) { if ( required ) { throw Error ( util . format ( "Source build %s not found, but required" , sourceBuildName ) ) ; } this . logger ?. debug ( `Source build ${ sourceBuildName } not found` ) ; let possibles = pipelines . filter ( p => p . name ?. startsWith ( `deploy-${ template }` ) ) ; if ( possibles . length > 0 ) { sourceBuildName = possibles [ 0 ] . name ; sourceBuild = possibles . length > 0 ? await client . getDefinition ( possibles [ 0 ] . project ?. name , possibles [ 0 ] . id ) : null ; this . logger ?. debug ( `Selecting ${ sourceBuildName } to copy settings from` ) ; } if ( sourceBuild == null ) { defaultSettings = true ; this . logger ?. debug ( `Matching ${ template } build not found, will apply default settings` ) ; this . logger ?. debug ( `Applying default service connection. You will need to update settings with you environment teams` ) ; sourceBuild = < BuildInterfaces . BuildDefinition > { } ; sourceBuild . repository = < BuildInterfaces . BuildRepository > { } ; sourceBuild . repository . id = repo . id ; sourceBuild . repository . name = repo . name ; sourceBuild . repository . url = repo . url ; sourceBuild . repository . type = 'TfsGit' ; let environmentName = '' ; let seviceConnection = '' ; let validationName = typeof ( args . settings [ "validation" ] === "string" ) ? args . settings [ "validation" ] : "yourenviromenthere-validation" ; let testName = typeof ( args . settings [ "test" ] === "string" ) ? args . settings [ "test" ] : "yourenviromenthere-test" ; let prodName = typeof ( args . settings [ "prod" ] === "string" ) ? args . settings [ "prod" ] : "yourenviromenthere-prod" ; switch ( template ?. toLowerCase ( ) ) { case "validation" : { environmentName = 'Validation' ; seviceConnection = Environment . getEnvironmentUrl ( validationName , args . settings ) ; break ; } case "test" : { environmentName = 'Test' ; seviceConnection = Environment . getEnvironmentUrl ( testName , args . settings ) ; break ; } case "prod" : { environmentName = 'Production' ; seviceConnection = Environment . getEnvironmentUrl ( prodName , args . settings ) ; break ; } } sourceBuild . variables = { EnvironmentName : < BuildInterfaces . BuildDefinitionVariable > { } , ServiceConnection : < BuildInterfaces . BuildDefinitionVariable > { } } ; sourceBuild . variables . EnvironmentName . value = environmentName ; sourceBuild . variables . ServiceConnection . value = seviceConnection ; } } this . logger ?. info ( util . format ( "Creating new pipeline %s" , destinationBuildName ) ) ; var newBuild = < BuildInterfaces . BuildDefinition > { } ; newBuild . name = destinationBuildName ; let process = < BuildInterfaces . YamlProcess > { } ; process . yamlFilename = util . format ( "/%s/%s.yml" , destination , destinationBuildName ) ; newBuild . process = process ; newBuild . path = "/" + destination ; newBuild . repository = sourceBuild . repository ; newBuild . repository . defaultBranch = createInBranch ; newBuild . variables = sourceBuild . variables ; if ( sourceBuild . triggers != null ) { newBuild . triggers = sourceBuild . triggers ; } if ( sourceBuild . queue != null ) { newBuild . queue = sourceBuild . queue ; } else { newBuild . queue = < BuildInterfaces . BuildDefinitionReference > { queue : defaultPool } ; } let result = await client . createDefinition ( newBuild , project . name ) ; if ( defaultSettings && args . openDefaultPages ) { await open ( `${ baseUrl }/_build/${ result ?. id }` ) ; } } async getGitCommitChanges ( destinationBranch , defaultBranch , templatesRepository , names ) { let results = [ ] ; for ( let i = 0 ; i < names . length ; i ++ ) { let url = util . format ( "https://raw.githubusercontent.com/microsoft/coe-alm-accelerator-templates/main/Pipelines/build-deploy-%s-SampleSolution.yml" , names [ i ] ) ; let response = await this . getUrl ( url ) ; let commit = < GitChange > { } ; commit . changeType = VersionControlChangeType . Add ; commit . item = < GitItem > { } ; commit . item . path = util . format ( "/%s/deploy-%s-%s.yml" , destinationBranch , names [ i ] , destinationBranch ) ; commit . newContent = < ItemContent > { } ; commit . newContent . content = ( response ) ?. replace ( /BranchContainingTheBuildTemplates/g , defaultBranch ) ; commit . newContent . content = ( commit . newContent . content ) ?. replace ( /RepositoryContainingTheBuildTemplates/g , templatesRepository ) ; commit . newContent . content = ( commit . newContent . content ) ?. replace ( /SampleSolutionName/g , destinationBranch ) ; commit . newContent . contentType = ItemContentType . RawText ; results . push ( commit ) ; } return results ; } } type GraphGroup = { /**
     * A short phrase to help human readers disambiguate groups with similar names
     */ description : string ; /**
     * The descriptor is the primary way to reference the graph subject while the system is running. This field will uniquely identify the same graph subject across both Accounts and Organizations.
     */ descriptor : string ; /**
     * This is the non-unique display name of the graph subject. To change this field, you must alter its value in the source provider.
     */ displayName : string ; /**
     * This represents the name of the container of origin for a graph member. (For MSA this is "Windows Live ID", for AD the name of the domain, for AAD the tenantID of the directory, for VSTS groups the ScopeId, etc)
     */ domain : string ; /**
     * [Internal Use Only] The legacy descriptor is here in case you need to access old version IMS using identity descriptor.
     */ legacyDescriptor : string ; /**
     * The email address of record for a given graph member. This may be different than the principal name.
     */ mailAddress : string ; /**
     * The type of source provider for the origin identifier (ex:AD, AAD, MSA)
     */ origin : string ; /**
     * The unique identifier from the system of origin. Typically a sid, object id or Guid. Linking and unlinking operations can cause this value to change for a user because the user is not backed by a different provider and has a different unique id in the new provider.
     */ originId : string ; /**
     * This is the PrincipalName of this graph member from the source provider. The source provider may change this field over time and it is not guaranteed to be immutable for the life of the graph member by VSTS.
     */ principalName : string ; /**
     * This field identifies the type of the graph subject (ex: Group, Scope, User).
     */ subjectKind : string ; /**
     * This url is the full route to the source resource of this graph subject.
     */ url : string ; } ; type MembershipResponse = { count : number ; value : GraphMembership [ ] ; } ; type GraphMembership = { containerDescriptor : string ; memberDescriptor : string ; } ; /**
 * Install Arguments
 */ class DevOpsInstallArguments { constructor ( ) { this . extensions = [ ] ; this . environments = { } ; this . azureActiveDirectoryServicePrincipal = 'ALMAcceleratorServicePrincipal' ; this . accessTokens = { } ; this . createSecretIfNoExist = true ; this . endpoint = "prod" ; this . settings = { } ; } /**
     * The Bearer Auth access token
     */ accessToken : string ; /**
     * Audance scoped access tokens
     */ accessTokens : { [ id ] : string ; } ; /**
     * The power platform endpoint type
     */ endpoint : string ; /**
     * The name of the Azure DevOps Organization
     */ organizationName : string ; /**
     * The name of repository that the Accelerator has been deployed to or will be deployed to
     */ repositoryName : string ; /**
     * The name of repository that the Accelerator pipeline has been deployed to or will be deployed to
     */ pipelineRepositoryName : string ; /**
     * The name of the project that the Accelerator has been deployed to
     */ projectName : string ; /**
     * The name of the Azure subscription. Required if access to more than one Azure subscription
     */ subscription : string ; /**
     * The client id to use for authentication
     */ clientId : string ; /**
     * The Azure Active directory application name will use to integrate with Azure DevOps
     */ azureActiveDirectoryServicePrincipal : string ; /**
     * The azure active directory makers group
    */ azureActiveDirectoryMakersGroup : string ; /**
     * The DevOps extension to install
     */ extensions : DevOpsExtension [ ] ; /**
     * Create secret if not exist
     */ createSecretIfNoExist : boolean ; /**
     * The Azure DevOps environment
     */ environment : string ; /**
    * The Azure DevOps environments
    */ environments : { [ id ] : string ; } ; /**
    * Optional settings
    */ settings : { [ id ] : string ; } ; /**
    * The user to associate with this connection
    */ user : string ; } type DevOpsExtension = { /**
     * The extension name
     */ name : string ; /**
     * The publisher name
     */ publisher : string ; } ; /**
 * Branch Arguments
 */ class DevOpsBranchArguments { constructor ( ) { this . settings = { } ; } /**
     * The Bearer Auth access token
     */ accessToken : string ; /**
     * Scoped access tokens
     */ accessTokens : { [ id ] : string ; } ; /**
     * The name of the Azure DevOps Organization
     */ organizationName : string ; /**
     * The name of repository that the Accelerator has been deployed to or will be deployed to
     */ repositoryName : string ; /**
     * The name of pipeline templates repository that the Accelerator has been imported
     */ pipelineRepository : string ; /**
     * The name of the project that the Accelerator has been deployed to
     */ projectName : string ; /**
     * The source branch to branch from (Optional). If not defined will use default branch
     */ sourceBranch : string ; /**
     * The source build name to copy setup from> if not defained will create initial values that will need to be updated
     */ sourceBuildName : string ; /**
     * The name of the branch to create
     */ destinationBranch : string ; /**
     * Open default web configuration pages
     */ openDefaultPages : boolean ; /**
    * Optional settings
    */ settings : { [ id ] : string ; } ; } type ServiceConnectorReference = string | any ; type DevOpsProjectSecurityContext = { /**
     * The project id
     */ projectId : string ; /**
     * The ALM Azure DevOps Group
     */ almGroup : GraphGroup ; /**
     * The Azure DevOps service for REST API calls
     */ securityUrl : string ; /**
     * The extension name
     */ projectDescriptor : string ; } ; export { DevOpsBranchArguments , DevOpsInstallArguments , DevOpsCommand , DevOpsExtension , DevOpsProjectSecurityContext } ;