const adal = require ( 'adal-node' ) ; import * as chalk from 'chalk' ; import * as fs from 'fs' ; import * as path from 'path' ; import * as os from 'os' ; import * as uuid from 'uuid' ; import * as forge from 'node-forge' ; import * as momemt from 'moment' ; import { ChoiceType , prompt } from 'inquirer' ; import { AuthResponse , AzureEnvironment , AzureTokenCredentialsOptions , DeviceTokenCredentials , DeviceTokenCredentialsOptions , LinkedSubscription , InteractiveLoginOptions , interactiveLoginWithAuthResponse , loginWithServicePrincipalSecretWithAuthResponse , UserTokenCredentials , ApplicationTokenCredentials } from 'ms-rest-azure' ; import { SubscriptionClient , SubscriptionModels , ResourceManagementClient , ResourceModels } from 'azure-arm-resource' ; import GraphRbacManagementClient from 'azure-graph' ; import AuthorizationManagementClient = require ( 'azure-arm-authorization' ) ; import ComputeManagementClient = require ( 'azure-arm-compute' ) ; import { Command } from 'commander' ; import { Answers , Question } from 'inquirer' ; import { DeploymentManager , IDeploymentManager } from './deploymentmanager' ; import DeployUI from './deployui' ; import { Questions , IQuestions } from './questions' ; import { genPassword } from './utils' ; import { Application , ServicePrincipal , ApplicationListResult , ApplicationCreateParameters , ServicePrincipalListResult } from 'azure-graph/lib/models' ; import { SubscriptionListResult } from 'azure-arm-resource/lib/subscription/models' ; import { TokenCredentials , ServiceClientCredentials } from 'ms-rest' ; import Validator from './validator' ; import inquirer = require ( 'inquirer' ) ; const WebSiteManagementClient = require ( 'azure-arm-website' ) ; const packageJson = require ( '../package.json' ) ; const solutionType = 'remotemonitoring' ; enum solutionSkus { basic , standard , local } const gitHubUrl = 'https://github.com/Azure/pcs-cli' ; const gitHubIssuesUrl = 'https://github.com/azure/pcs-cli/issues/new' ; const pcsTmpDir = os . homedir ( ) + path . sep + '.pcs' ; const cacheFilePath = pcsTmpDir + path . sep + 'cache.json' ; const defaultSshPublicKeyPath = os . homedir ( ) + path . sep + '.ssh' + path . sep + 'id_rsa.pub' ; const MAX_RETRYCOUNT = 36 ; const RELEASE_VERSION_PATTERN = /((\d*\.){2}(\d*\-preview.)(\d*))(\.\d*)*$/ ; let cachedAuthResponse ; let answers = { } ; let userPrincipalObjectId ; const program = new Command ( packageJson . name ) . version ( packageJson . version , '-v, --version' ) . option ( '-t, --type <type>' , 'Solution Type: remotemonitoring, devicesimulation, devicesimulation-nohub' , /^(remotemonitoring|devicesimulation|devicesimulation-nohub|test)$/i , 'remotemonitoring' ) . option ( '-s, --sku <sku>' , 'SKU Type (only for Remote Monitoring): basic, standard, or local' , /^(basic|standard|local)$/i , 'basic' ) . option ( '-e, --environment <environment>' , 'Azure environments: AzureCloud or AzureChinaCloud' , /^(AzureCloud|AzureChinaCloud)$/i , 'AzureCloud' ) . option ( '-r, --runtime <runtime>' , 'Microservices runtime (only for Remote Monitoring): dotnet or java' , /^(dotnet|java)$/i , 'dotnet' ) . option ( '--servicePrincipalId <servicePrincipalId>' , 'Service Principal Id' ) . option ( '--servicePrincipalSecret <servicePrincipalSecret>' , 'Service Principal Secret' ) . option ( '--versionOverride <versionOverride>' , 'Current accepted value is "master"' ) . option ( '--dockerTagOverride <dockerTagOverride>' , 'Override value for Docker image tag' ) . option ( '--domainId <domainId>' , 'This can either be an .onmicrosoft.com domain or the Azure object ID for the tenant' ) . option ( '--solutionName <solutionName>' , 'Solution name for your Remote monitoring accelerator' ) . option ( '--subscriptionId <subscriptionId>' , 'SubscriptionId on which this solution should be created' ) . option ( '-l, --location <location>' , 'Location where the solution will be deployed' ) . option ( '-w, --websiteName <websiteName>' , 'Name of the website, default is solution name' ) . option ( '-u, --username <username>' , 'User name for the virtual machine that will be created as part of the solution' ) . option ( '-p, --password <password>' , 'Password for the virtual machine that will be created as part of the solution' ) . option ( '--appInsightsInstrumentationKey <appInsightsInstrumentationKey>' , 'AppInsights Instrumentation Key for the diagnostics service' ) . on ( '--help' , ( ) => { console . log ( `    Default value for ${ chalk . green ( '-t, --type' ) } is ${ chalk . green ( 'remotemonitoring' ) }.` ) ; console . log ( `    Default value for ${ chalk . green ( '-s, --sku' ) } is ${ chalk . green ( 'basic' ) }.` ) ; console . log ( `    Example for deploying Remote Monitoring Basic:  ${ chalk . green ( 'pcs -t remotemonitoring -s basic' ) }.` ) ; console . log ( `    Example for deploying Remote Monitoring Standard:  ${ chalk . green ( 'pcs -t remotemonitoring -s standard' ) }.` ) ; console . log ( `    Example for deploying Remote Monitoring for local development:  ${ chalk . green ( 'pcs -t remotemonitoring -s local' ) }.` ) ; console . log ( `    Example for deploying Device Simulation:  ${ chalk . green ( 'pcs -t devicesimulation' ) }.` ) ; console . log ( `    Example for deploying Device Simulation:  ${ chalk . green ( 'pcs -t devicesimulation-nohub' ) }.` ) ; console . log ( ) ; console . log ( '  Commands:' ) ; console . log ( ) ; console . log ( '    login:         Log in to access Azure subscriptions.' ) ; console . log ( '    logout:        Log out to remove access to Azure subscriptions.' ) ; console . log ( ) ; console . log ( `    For further documentation, please visit:` ) ; console . log ( `    ${ chalk . cyan ( gitHubUrl ) }` ) ; console . log ( `    If you have any problems please file an issue:` ) ; console . log ( `    ${ chalk . cyan ( gitHubIssuesUrl ) }` ) ; console . log ( ) ; } ) . parse ( process . argv ) ; if ( ! program . args [ 0 ] || program . args [ 0 ] === '-t' ) { if ( program . servicePrincipalId ) { if ( ! program . servicePrincipalSecret ) { console . log ( 'If servicePrincipalId is provided then servicePrincipalSecret is also required' ) ; } else { const tokenCredentialsOptions = { environment : getAzureEnvironment ( program . environment ) } ; loginWithServicePrincipalSecretWithAuthResponse ( program . servicePrincipalId , program . servicePrincipalSecret , program . domainId , tokenCredentialsOptions ) . then ( ( response ) => { saveAuthResponse ( response ) ; main ( ) ; } ) . catch ( ( error ) => { console . log ( error ) ; } ) ; } } else { main ( ) ; } } else if ( program . args [ 0 ] === 'login' ) { login ( ) ; } else if ( program . args [ 0 ] === 'logout' ) { logout ( ) ; } else { console . log ( `${ chalk . red ( 'Invalid choice:' , program . args . toString ( ) ) }` ) ; console . log ( 'For help, %s' , `${ chalk . yellow ( 'pcs -h' ) }` ) ; } function main ( ) { /** Pre-req
     * Login through https://aka.ms/devicelogin and code prompt
     */ /** Data needed to create template deployment
     * Get solution/resourceGroup name
     * Get user name and pwd if required
     * Get the local arm template
     * Create parameters json from options so far
     * Subscriptions list from either DeviceTokenCredentials or SubscriptionsManagementClient
     * Get location information
     */ /** Actions on data collected
     * Create resource group
     * Submit deployment
     */ cachedAuthResponse = cachedAuthResponse || getCachedAuthResponse ( ) ; if ( ! cachedAuthResponse || ! program . servicePrincipalId && cachedAuthResponse . isServicePrincipal ) { console . log ( 'Please run %s' , `${ chalk . yellow ( 'pcs login' ) }` ) ; } else { const baseUri = cachedAuthResponse . credentials . environment . resourceManagerEndpointUrl ; getSubscriptionList ( ) . then ( ( subscriptionList ) => { const subs = [ ] ; cachedAuthResponse . linkedSubscriptions . map ( ( subscription ) => { if ( subscription . state === 'Enabled' ) { subs . push ( { name : subscription . name , value : subscription . id } ) ; } } ) ; if ( ! subs || ! subs . length ) { console . log ( 'Could not find any subscriptions in this account.' ) ; console . log ( 'Please login with an account that has at least one active subscription' ) ; } else { let solutionNameAns ; if ( program . solutionName ) { if ( ! Validator . validateSolutionName ( program . solutionName ) ) { throw new Error ( Validator . invalidSolutionNameMessage ) ; } solutionNameAns = Promise . resolve < Answers > ( { solutionName : program . solutionName } ) ; } else { const solutionNameQuestion = new Questions ( ) ; solutionNameQuestion . addQuestion ( getSolutionNameQuestion ( ) ) ; solutionNameAns = prompt ( solutionNameQuestion . value ) ; } return solutionNameAns . then ( ( solutionNameAnswer ) => { program . solutionName = solutionNameAnswer . solutionName ; const questions = new Questions ( ) ; questions . addQuestion ( getSelectSubscriptionQuestion ( subs ) ) ; const deployUI = DeployUI . instance ; let deploymentManager ; let subPrompt ; if ( program . servicePrincipalId || program . subscriptionId ) { subPrompt = Promise . resolve < Answers > ( { azureWebsiteName : program . websiteName || program . solutionName , location : program . location , solutionName : program . solutionName , subscriptionId : program . subscriptionId } ) ; } else { subPrompt = prompt ( questions . value ) ; } return subPrompt . then ( ( ans ) => { answers = ans ; answers . solutionName = program . solutionName ; const index = cachedAuthResponse . linkedSubscriptions . findIndex ( ( x ) => x . id === answers . subscriptionId ) ; if ( index === - 1 ) { const errorMessage = 'Selected subscriptionId was not found in cache' ; console . log ( errorMessage ) ; throw new Error ( errorMessage ) ; } cachedAuthResponse . credentials . domain = cachedAuthResponse . linkedSubscriptions [ index ] . tenantId ; answers . domainName = cachedAuthResponse . credentials . domain ; const serviceClientCredentials = cachedAuthResponse . isServicePrincipal ? cachedAuthResponse . credentials : new DeviceTokenCredentials ( cachedAuthResponse . credentials ) ; deploymentManager = new DeploymentManager ( serviceClientCredentials , cachedAuthResponse . credentials . environment , answers . subscriptionId , program . type , program . sku ) ; return deploymentManager . getLocations ( ) ; } ) . then ( ( locations ) => { if ( program . location && ( program . websiteName || program . solutionName ) ) { const ans = { adminUsername : program . username , azureWebsiteName : program . websiteName || program . solutionName , location : program . location , solutionName : program . solutionName } ; setUserCredential ( ans ) ; return Promise . resolve < Answers > ( ans ) ; } else if ( locations && locations . length > 0 ) { return prompt ( getDeploymentQuestions ( locations ) ) ; } throw new Error ( 'Locations list cannot be empty' ) ; } ) . then ( ( ans ) => { if ( program . environment === AzureEnvironment . AzureChina . name ) { return Promise . resolve ( ans ) ; } const resourceManagementClient = new ResourceManagementClient ( cachedAuthResponse . isServicePrincipal ? cachedAuthResponse . credentials : new DeviceTokenCredentials ( cachedAuthResponse . credentials ) , answers . subscriptionId , baseUri ) ; ans . telemetryStorageType = 'tsi' ; const promises = new Array < Promise < any > > ( ) ; promises . push ( resourceManagementClient . providers . get ( 'Microsoft.TimeSeriesInsights' ) . then ( ( providers ) => { if ( providers . resourceTypes ) { const resourceType = providers . resourceTypes . filter ( ( x ) => x . resourceType && x . resourceType . toLowerCase ( ) === 'environments' ) ; if ( resourceType && resourceType . length ) { if ( new Set ( resourceType [ 0 ] . locations ) . has ( ans . location ) ) { ans . tsiLocation = ans . location . split ( ' ' ) . join ( '' ) . toLowerCase ( ) ; } } } } ) ) ; promises . push ( resourceManagementClient . providers . get ( 'Microsoft.Devices' ) . then ( ( providers ) => { if ( providers . resourceTypes ) { const resourceType = providers . resourceTypes . filter ( ( x ) => x . resourceType && x . resourceType . toLowerCase ( ) === 'provisioningservices' ) ; if ( resourceType && resourceType . length ) { if ( new Set ( resourceType [ 0 ] . locations ) . has ( ans . location ) ) { ans . provisioningServiceLocation = ans . location . split ( ' ' ) . join ( '' ) . toLowerCase ( ) ; } } } } ) ) ; return Promise . all ( promises ) . then ( ( ) => Promise . resolve ( ans ) ) ; } ) . then ( ( ans ) => { answers = { ... answers , ... ans } ; if ( ans . pwdFirstAttempt !== ans . pwdSecondAttempt ) { return askPwdAgain ( ) ; } return ans ; } ) . then ( ( ans ) => { if ( program . sku . toLowerCase ( ) === solutionSkus [ solutionSkus . local ] ) { answers . azureWebsiteName = answers . solutionName || program . solutionName ; } else { answers . adminPassword = ans . pwdFirstAttempt ; answers . sshFilePath = ans . sshFilePath ; } deployUI . start ( 'Registering application in the Azure Active Directory' ) ; return createServicePrincipal ( answers . azureWebsiteName , answers . subscriptionId , cachedAuthResponse . credentials , cachedAuthResponse . isServicePrincipal ) ; } ) . then ( ( { appId , domainName , objectId , servicePrincipalId , servicePrincipalSecret } ) => { cachedAuthResponse . credentials . tokenAudience = null ; answers . deploymentSku = program . sku ; answers . runtime = program . runtime ; answers . deploymentId = uuid . v1 ( ) ; answers . appInsightsInstrumentationKey = program . appInsightsInstrumentationKey ; answers . userPrincipalObjectId = userPrincipalObjectId ; if ( program . versionOverride && program . dockerTagOverride ) { answers . version = program . versionOverride ; answers . dockerTag = program . dockerTagOverride ; } else if ( program . versionOverride ) { answers . version = program . versionOverride ; answers . dockerTag = 'testing' ; } else if ( program . dockerTagOverride ) { answers . dockerTag = program . dockerTagOverride ; answers . version = 'master' ; } else { const version = ( program . type === 'remotemonitoring' ) ? '3.0.0' : 'DS-2.0.2' ; answers . version = version ; answers . dockerTag = version ; } if ( appId && servicePrincipalSecret ) { const env = cachedAuthResponse . credentials . environment ; const appUrl = `${ env . portalUrl }/${ domainName }#blade/Microsoft_AAD_IAM/ApplicationBlade/objectId/${ objectId }/appId/${ appId }` ; deployUI . stop ( { message : `Application registered: ${ chalk . cyan ( appUrl ) } ` } ) ; answers . appId = appId ; answers . aadAppUrl = appUrl ; answers . servicePrincipalId = servicePrincipalId ; answers . servicePrincipalSecret = servicePrincipalSecret ; answers . certData = createCertificate ( ) ; answers . aadTenantId = cachedAuthResponse . credentials . domain ; answers . domainName = domainName ; return deploymentManager . submit ( answers ) ; } else { const message = 'To create a service principal, you must have permissions to register an ' + 'application with your Azure Active Directory (AAD) tenant, and to assign ' + 'the application to a role in your subscription. To see if you have the ' + 'required permissions, check here https://docs.microsoft.com/en-us/azure/azure-resource-manager/' + 'resource-group-create-service-principal-portal#required-permissions.' ; console . log ( `${ chalk . red ( message ) }` ) ; } } ) . then ( ( ) => { deployUI . close ( ) ; } ) . catch ( ( error ) => { if ( error . request ) { console . log ( JSON . stringify ( error , null , 2 ) ) ; } else { console . log ( error ) ; } } ) ; } ) ; } } ) . catch ( ( error ) => { if ( error && error . message ) { console . log ( error . message ) ; } else { console . log ( 'Please run %s' , `${ chalk . yellow ( '\"pcs login\"' ) }` ) ; } } ) ; } } function getSubscriptionList ( ) { const baseUri = cachedAuthResponse . credentials . environment . resourceManagerEndpointUrl ; const client = cachedAuthResponse . isServicePrincipal ? new SubscriptionClient ( cachedAuthResponse . credentials , baseUri ) : new SubscriptionClient ( new DeviceTokenCredentials ( cachedAuthResponse . credentials ) , baseUri ) ; return client . subscriptions . list ( ) ; } function login ( ) { const environment = getAzureEnvironment ( program . environment ) ; const loginOptions = { environment } ; return interactiveLoginWithAuthResponse ( loginOptions ) . then ( ( response ) => { saveAuthResponse ( response ) ; } ) . catch ( ( error ) => { console . log ( error ) ; } ) ; } function logout ( ) { if ( fs . existsSync ( cacheFilePath ) ) { fs . unlinkSync ( cacheFilePath ) ; } console . log ( `${ chalk . green ( 'Successfully logged out' ) }` ) ; } function saveAuthResponse ( response ) { const isServicePrincipal = response . credentials . constructor . name === 'ApplicationTokenCredentials' ; const data = { credentials : response . credentials , isServicePrincipal , linkedSubscriptions : response . subscriptions } ; cachedAuthResponse = data ; if ( ! fs . existsSync ( pcsTmpDir ) ) { fs . mkdirSync ( pcsTmpDir ) ; } fs . writeFileSync ( cacheFilePath , JSON . stringify ( data ) ) ; console . log ( `${ chalk . green ( 'Successfully logged in' ) }` ) ; } function getCachedAuthResponse ( ) { if ( ! fs . existsSync ( cacheFilePath ) ) { return null ; } else { const cache = JSON . parse ( fs . readFileSync ( cacheFilePath , 'UTF-8' ) ) ; const tokenCache = new adal . MemoryCache ( ) ; const credentials = cache . credentials ; tokenCache . add ( credentials . tokenCache . _entries , ( ) => { } ) ; credentials . tokenCache = tokenCache ; program . environment = credentials . environment && credentials . environment . name ; return { credentials , isServicePrincipal : cache . isServicePrincipal , linkedSubscriptions : cache . linkedSubscriptions } ; } } function createServicePrincipal ( azureWebsiteName , subscriptionId , options , usingServicePrincipal ) { const homepage = getWebsiteUrl ( azureWebsiteName ) ; const baseUri = options . environment ? options . environment . activeDirectoryGraphResourceId : 'https://graph.windows.net/' ; const existingServicePrincipalSecret = program . servicePrincipalSecret ; const newServicePrincipalSecret = genPassword ( ) ; const adminAppRoleId = 'a400a00b-f67c-42b7-ba9a-f73d8c67e433' ; const readOnlyAppRoleId = 'e5bbd0f5-128e-4362-9dd1-8f253c6082d7' ; let newServicePrincipal ; let objectId = '' ; let servicePrincipalCreateParameters ; let graphClient ; options . tokenAudience = 'graph' ; let graphClientPromise ; if ( usingServicePrincipal ) { graphClientPromise = loginWithServicePrincipalSecretWithAuthResponse ( program . servicePrincipalId , program . servicePrincipalSecret , program . domainId , options ) . then ( ( response ) => { return new GraphRbacManagementClient ( response . credentials , options . domain || '' , baseUri ) ; } ) ; } else { graphClient = new GraphRbacManagementClient ( new DeviceTokenCredentials ( options ) , options . domain || '' , baseUri ) ; graphClientPromise = Promise . resolve < GraphRbacManagementClient > ( graphClient ) ; } return graphClientPromise . then ( ( client ) => { graphClient = client ; const startDate = new Date ( Date . now ( ) ) ; let endDate = new Date ( startDate . toISOString ( ) ) ; const m = momemt ( endDate ) ; m . add ( 1 , 'years' ) ; endDate = new Date ( m . toISOString ( ) ) ; const identifierUris = [ homepage ] ; const replyUrls = [ homepage ] ; const requiredResourceAccess = [ { resourceAccess : [ { id : '311a71cc-e848-46a1-bdf8-97ff7156d8e6' , type : 'Scope' } ] , resourceAppId : '00000002-0000-0000-c000-000000000000' } ] ; const applicationCreateParameters = { appRoles : [ { allowedMemberTypes : [ 'User' ] , description : 'Administrator access to the application' , displayName : 'Admin' , id : adminAppRoleId , isEnabled : true , value : 'Admin' } , { allowedMemberTypes : [ 'User' ] , description : 'Read only access to device information' , displayName : 'Read Only' , id : readOnlyAppRoleId , isEnabled : true , value : 'ReadOnly' } ] , availableToOtherTenants : false , displayName : azureWebsiteName , homepage , identifierUris , oauth2AllowImplicitFlow : true , optionalClaims : { idToken : [ { essential : true , name : 'role' } ] } , passwordCredentials : [ { endDate , keyId : uuid . v1 ( ) , startDate , value : newServicePrincipalSecret } ] , replyUrls , requiredResourceAccess } ; return graphClient . applications . create ( applicationCreateParameters ) . then ( ( result ) => { return result ; } ) . catch ( ( error ) => { throw new Error ( `Could not create new application in this tenant: ${ error . message || ( error . body && error . body . message ) }` ) ; } ) ; } ) . then ( ( result ) => { servicePrincipalCreateParameters = { accountEnabled : true , appId : result . appId } ; objectId = result . objectId ; return graphClient . servicePrincipals . create ( servicePrincipalCreateParameters ) ; } ) . then ( ( sp ) => { newServicePrincipal = sp ; if ( usingServicePrincipal ) { userPrincipalObjectId = program . servicePrincipalId ; return Promise . resolve ( newServicePrincipal ) ; } return createAppRoleAssignment ( adminAppRoleId , sp , graphClient , baseUri ) ; } ) . then ( ( sp ) => { const appId = sp . appId ; return graphClient . domains . list ( ) . then ( ( domains ) => { let domainName = '' ; const servicePrincipalId = newServicePrincipal . objectId || program . servicePrincipalId ; const servicePrincipalSecret = newServicePrincipalSecret || existingServicePrincipalSecret ; domains . forEach ( ( value ) => { if ( value . isDefault ) { domainName = value . name ; } } ) ; return { appId , domainName , objectId , servicePrincipalId , servicePrincipalSecret } ; } ) ; } ) . catch ( ( error ) => { throw error ; } ) ; } function createAppRoleAssignment ( roleId , sp , graphClient , baseUri ) { const meOptions = { method : 'GET' , url : `${ baseUri }/me?api-version=1.6` } ; return graphClient . sendRequest ( meOptions ) . then ( ( me ) => { userPrincipalObjectId = me . objectId ; const options = { body : { id : roleId , principalId : me . objectId , resourceId : sp . objectId , } , method : 'POST' , url : `${ baseUri }/me/appRoleAssignments?api-version=1.6` , } ; return graphClient . sendRequest ( options ) . then ( ( result ) => { return sp ; } ) . catch ( ( error ) => { throw new Error ( 'Could not assign app admin role to you' ) ; } ) ; } ) ; } function createCertificate ( ) { const pki = forge . pki ; const keys = pki . rsa . generateKeyPair ( 2048 ) ; const certificate = pki . createCertificate ( ) ; certificate . publicKey = keys . publicKey ; certificate . serialNumber = '01' ; certificate . validity . notBefore = new Date ( Date . now ( ) ) ; certificate . validity . notAfter = new Date ( Date . now ( ) ) ; certificate . validity . notAfter . setFullYear ( certificate . validity . notBefore . getFullYear ( ) + 1 ) ; certificate . sign ( keys . privateKey ) ; const cert = forge . pki . certificateToPem ( certificate ) ; const fingerPrint = forge . md . sha1 . create ( ) . update ( forge . asn1 . toDer ( pki . certificateToAsn1 ( certificate ) ) . getBytes ( ) ) . digest ( ) . toHex ( ) ; return { cert , fingerPrint , key : forge . pki . privateKeyToPem ( keys . privateKey ) } ; } function getDeploymentQuestions ( locations ) { const questions = [ ] ; questions . push ( { choices : locations , message : 'Select a location:' , name : 'location' , type : 'list' , } ) ; if ( program . sku . toLowerCase ( ) !== solutionSkus [ solutionSkus . local ] ) { questions . push ( { default : ( ) : any => { return answers . solutionName ; } , message : 'Enter prefix for ' + getDomain ( ) + ':' , name : 'azureWebsiteName' , type : 'input' , validate : ( value ) => { if ( ! value . match ( Validator . websiteHostNameRegex ) ) { return 'Please enter a valid prefix for azure website.\n' + 'Valid characters are: ' + 'alphanumeric (A-Z, a-z, 0-9), ' + 'and hyphen(-)' ; } return checkUrlExists ( value , answers . subscriptionId ) ; } } ) ; } if ( program . sku . toLowerCase ( ) === solutionSkus [ solutionSkus . basic ] ) { questions . push ( { message : 'Enter a user name for the virtual machine:' , name : 'adminUsername' , type : 'input' , validate : ( userName ) => { const pass = userName . match ( Validator . userNameRegex ) ; const notAllowedUserNames = Validator . notAllowedUserNames . filter ( ( u ) => { return u === userName ; } ) ; if ( pass && notAllowedUserNames . length === 0 ) { return true ; } return Validator . invalidUsernameMessage ; } , } ) ; questions . push ( pwdQuestion ( 'pwdFirstAttempt' ) ) ; questions . push ( pwdQuestion ( 'pwdSecondAttempt' , 'Confirm your password:' ) ) ; } return questions ; } function getSelectSubscriptionQuestion ( subs ) { return { choices : subs , message : 'Select a subscription:' , name : 'subscriptionId' , type : 'list' } ; } function getSolutionNameQuestion ( ) { return { message : 'Enter a solution name:' , name : 'solutionName' , type : 'input' , validate : ( value ) => { const pass = value . match ( Validator . solutionNameRegex ) ; if ( pass ) { return true ; } return 'Please enter a valid solution name.\n' + 'Valid characters are: ' + 'alphanumeric (A-Z, a-z, 0-9), ' + 'underscore (_), parentheses, ' + 'hyphen(-), ' + 'and period (.) except at the end of the solution name.' ; } } ; } function pwdQuestion ( name , message ? ) { if ( ! message ) { message = 'Enter a password for the virtual machine:' ; } return { mask : '*' , message , name , type : 'password' , validate : ( password ) => { const pass = password . match ( Validator . passwordRegex ) ; const notAllowedPasswords = Validator . notAllowedPasswords . filter ( ( p ) => { return p === password ; } ) ; if ( pass && notAllowedPasswords . length === 0 ) { return true ; } return Validator . invalidPasswordMessage ; } } ; } function askPwdAgain ( ) { const questions = [ pwdQuestion ( 'pwdFirstAttempt' , 'Password did not match, please enter again:' ) , pwdQuestion ( 'pwdSecondAttempt' , 'Confirm your password:' ) ] ; return prompt ( questions ) . then ( ( ans ) => { if ( ans . pwdFirstAttempt !== ans . pwdSecondAttempt ) { return askPwdAgain ( ) ; } return ans ; } ) ; } function setUserCredential ( ans ) { if ( program . sku . toLowerCase ( ) === solutionSkus [ solutionSkus . basic ] ) { if ( program . username ) { if ( ! Validator . validateUsername ( program . username ) ) { throw new Error ( Validator . invalidUsernameMessage ) ; } ans . usernmae = program . username ; } else { throw new Error ( 'username is required for basic deployment' ) ; } if ( program . password ) { if ( ! Validator . validatePassword ( program . password ) ) { throw new Error ( Validator . invalidPasswordMessage ) ; } ans . pwdFirstAttempt = program . password ; ans . pwdSecondAttempt = program . password ; } else { throw new Error ( 'password is required for basic deployment' ) ; } } } function checkUrlExists ( hostName , subscriptionId ) { const baseUri = cachedAuthResponse . credentials . environment . resourceManagerEndpointUrl ; const client = new WebSiteManagementClient ( new DeviceTokenCredentials ( cachedAuthResponse . credentials ) , subscriptionId , baseUri ) ; return client . checkNameAvailability ( hostName , 'Site' ) . then ( ( result ) => { if ( ! result . nameAvailable ) { return result . message ; } return result . nameAvailable ; } ) . catch ( ( err ) => { return true ; } ) ; } function getDomain ( ) { let domain = '.azurewebsites.net' ; switch ( program . environment ) { case AzureEnvironment . Azure . name : domain = '.azurewebsites.net' ; break ; case AzureEnvironment . AzureChina . name : domain = '.chinacloudsites.cn' ; break ; case AzureEnvironment . AzureGermanCloud . name : domain = '.azurewebsites.de' ; break ; case AzureEnvironment . AzureUSGovernment . name : domain = '.azurewebsites.us' ; break ; default : domain = '.azurewebsites.net' ; break ; } return domain ; } function getWebsiteUrl ( hostName ) { const domain = getDomain ( ) ; return `https://${ hostName }${ domain }` ; } function getAzureEnvironment ( environmentName ) { const azureEnvironmentMaps = { azurechinacloud : AzureEnvironment . AzureChina , azurecloud : AzureEnvironment . Azure , azuregermancloud : AzureEnvironment . AzureGermanCloud , azureusgovernment : AzureEnvironment . AzureUSGovernment , } ; return azureEnvironmentMaps [ environmentName . toLowerCase ( ) ] ; }