import { Host , HostV2 } from './../models/host' ; import { HttpMethods , HttpConstants , LogCategories , ContainerConstants , ARMApiVersions , FunctionAppVersion , WorkerRuntimeLanguages , } from './../models/constants' ; import { UserService } from './user.service' ; import { HostingEnvironment , InternalLoadBalancingMode } from './../models/arm/hosting-environment' ; import { FunctionAppContext } from './../function-app-context' ; import { CacheService } from 'app/shared/services/cache.service' ; import { Injectable , Injector } from '@angular/core' ; import { Headers , Response , ResponseType , ResponseContentType } from '@angular/http' ; import { FunctionInfo } from 'app/shared/models/function-info' ; import { HttpResult } from './../models/http-result' ; import { ArmObj } from 'app/shared/models/arm/arm-obj' ; import { FunctionsVersionInfoHelper , runtimeIsV1 } from 'app/shared/models/functions-version-info' ; import { Constants } from 'app/shared/models/constants' ; import { ArmUtil } from 'app/shared/Utilities/arm-utils' ; import { Observable } from 'rxjs/Observable' ; import 'rxjs/add/observable/zip' ; import { ApiProxy } from 'app/shared/models/api-proxy' ; import * as jsonschema from 'jsonschema' ; import { VfsObject } from 'app/shared/models/vfs-object' ; import { FunctionTemplate } from 'app/shared/models/function-template' ; import { HttpRunModel , Param } from 'app/shared/models/http-run' ; import { FunctionKeys , FunctionKey } from 'app/shared/models/function-key' ; import { BindingConfig , RuntimeExtension } from 'app/shared/models/binding' ; import { HostStatus } from 'app/shared/models/host-status' ; import { SiteConfig } from 'app/shared/models/arm/site-config' ; import { FunctionAppEditMode } from 'app/shared/models/function-app-edit-mode' ; import { Site } from 'app/shared/models/arm/site' ; import { AuthSettings } from 'app/shared/models/auth-settings' ; import { RunFunctionResult } from 'app/shared/models/run-function-result' ; import { PortalResources } from 'app/shared/models/portal-resources' ; import { ConditionalHttpClient } from 'app/shared/conditional-http-client' ; import { TranslateService } from '@ngx-translate/core' ; import { errorIds } from 'app/shared/models/error-ids' ; import { LogService } from './log.service' ; import { ExtensionInstallStatus } from '../models/extension-install-status' ; import { SiteService } from './site.service' ; import { ExtensionJobsStatus } from '../models/extension-jobs-status' ; import { ExtensionInfo , ExtensionsJson } from 'app/shared/models/extension-info' ; import { Version } from 'app/shared/Utilities/version' ; import { ApplicationSettings } from 'app/shared/models/arm/application-settings' ; import { ArmSiteDescriptor } from '../resourceDescriptors' ; import { FunctionService } from './function.service' ; import { AppSettingsHelper } from '../Utilities/application-settings-helper' ; import { ArmService } from './arm.service' ; type Result < T > = Observable < HttpResult < T > > ; @ Injectable ( ) export class FunctionAppService { private readonly runtime : ConditionalHttpClient ; private readonly azure : ConditionalHttpClient ; constructor ( private _cacheService , private _translateService , private _userService , private _injector , private _siteService , private _logService , private _functionService , private _armService , injector ) { this . runtime = new ConditionalHttpClient ( injector , resourceId => this . getRuntimeToken ( resourceId ) , 'NoClientCertificate' , 'NotOverQuota' , 'NotStopped' , 'ReachableLoadballancer' ) ; this . azure = new ConditionalHttpClient ( injector , _ => this . _userService . getStartupInfo ( ) . map ( i => i . token ) , 'NotOverQuota' , 'ReachableLoadballancer' ) ; } private getRuntimeToken ( resourceId ) { return this . _functionService . getHostKeys ( resourceId ) . map ( hostKeys => { return ( hostKeys && hostKeys . result && `masterKey ${ hostKeys . result . masterKey }` ) || '' ; } ) ; } getClient ( context ) { return context . urlTemplates . useNewUrls ? this . runtime : this . azure ; } getApiProxies ( context , runtimeVersion ) { return this . getClient ( context ) . execute ( { resourceId : context . site . id } , token => Observable . zip ( this . retrieveProxies ( context , runtimeVersion ) , this . _cacheService . get ( 'assets/schemas/proxies.json' , false , this . portalHeaders ( token ) ) , ( p , s ) => ( { proxies : p , schema : s } ) ) . flatMap ( response => this . validateAndGetProxies ( response . proxies , response . schema ) ) ) ; } private retrieveProxies ( context , runtimeVersion ) { const headers = this . _armService . getHeaders ( ) ; headers [ 'Cache-Control' ] = 'no-cache' ; const resourceId = `${ context . site . id }${ context . urlTemplates . getProxiesVfsUrl ( runtimeVersion , 'proxies.json' ) }` ; const url = this . _armService . getArmUrl ( resourceId , this . _armService . antaresApiVersion20181101 ) ; return this . _cacheService . get ( url , true , headers ) . catch ( err => err . status === 404 ? Observable . throw ( { errorId : errorIds . proxyJsonNotFound , message : '' , result : null , } ) : Observable . throw ( err ) ) ; } private validateAndGetProxies ( proxiesResponse , schemaResponse ) { let proxiesJson , schemaJson ; try { proxiesJson = proxiesResponse . json ( ) ; } catch ( exception ) { return Observable . throw ( { errorId : errorIds . proxyJsonNotValid , message : this . _translateService . instant ( PortalResources . proxyJsonInvalid ) . format ( exception . message ) , result : null , } ) ; } try { schemaJson = schemaResponse . json ( ) ; } catch ( exception ) { return Observable . throw ( { errorId : errorIds . proxySchemaNotValid , message : this . _translateService . instant ( PortalResources . schemaJsonInvalid ) . format ( exception . message ) , result : null , } ) ; } if ( proxiesJson ) { const validateResult = jsonschema . validate ( proxiesJson , schemaJson ) ; if ( ! validateResult . valid ) { return Observable . throw ( { errorId : errorIds . proxySchemaValidationFails , message : this . _translateService . instant ( PortalResources . error_schemaValidationProxies ) . format ( validateResult . toString ( ) ) , result : null , } ) ; } } return Observable . of ( ApiProxy . fromJson ( proxiesJson ) ) ; } saveApiProxy ( context , jsonString , runtimeVersion ) { const headers = this . _armService . getHeaders ( ) ; headers . delete ( 'If-None-Match' ) ; headers . append ( 'Cache-Control' , 'no-cache' ) ; headers . append ( 'If-Match' , '*' ) ; const resourceId = `${ context . site . id }${ context . urlTemplates . getProxiesVfsUrl ( runtimeVersion , 'proxies.json' ) }` ; const url = this . _armService . getArmUrl ( resourceId , this . _armService . antaresApiVersion20181101 ) ; return this . getClient ( context ) . execute ( { resourceId : context . site . id } , t => this . _cacheService . put ( url , headers , jsonString ) ) ; } getFileContent ( context , file ) { const fileHref = typeof file === 'string' ? file : file . href ; return this . getClient ( context ) . execute ( { resourceId : context . site . id } , t => this . _cacheService . get ( fileHref , false , this . headers ( t ) ) . map ( r => r . text ( ) ) ) ; } saveFile ( context , file , updatedContent ) { const fileHref = typeof file === 'string' ? file : file . href ; return this . getClient ( context ) . execute ( { resourceId : context . site . id } , t => this . _cacheService . put ( fileHref , this . jsonHeaders ( t , [ 'Content-Type' , 'plain/text' ] , [ 'If-Match' , '*' ] ) , updatedContent ) . map ( ( ) => file ) ) ; } deleteFile ( context , file , functionInfo ? ) { const fileHref = typeof file === 'string' ? file : file . href ; return this . getClient ( context ) . execute ( { resourceId : context . site . id } , t => this . _cacheService . delete ( fileHref , this . jsonHeaders ( t , [ 'Content-Type' , 'plain/text' ] , [ 'If-Match' , '*' ] ) ) . map ( ( ) => file ) ) ; } getRuntimeGeneration ( context ) { return this . getExtensionVersionFromAppSettings ( context ) . map ( v => FunctionsVersionInfoHelper . getFunctionGeneration ( v ) ) . catch ( e => { return Observable . of ( FunctionAppVersion . v2 ) ; } ) ; } private getExtensionVersionFromAppSettings ( context ) { return this . _cacheService . postArm ( `${ context . site . id }/config/appsettings/list` ) . map ( r => { const appSettingsArm = r . json ( ) ; return appSettingsArm . properties [ Constants . runtimeVersionAppSettingName ] ; } ) ; } getTemplates ( context ) { try { if ( localStorage . getItem ( 'dev-templates' ) ) { const devTemplate = JSON . parse ( localStorage . getItem ( 'dev-templates' ) ) ; this . localize ( devTemplate ) ; return Observable . of ( { isSuccessful : true , result : devTemplate , error : null , } ) ; } } catch ( e ) { console . error ( e ) ; } return this . azure . executeWithConditions ( [ ] , { resourceId : context . site . id } , t => this . getExtensionVersionFromAppSettings ( context ) . mergeMap ( extensionVersion => { if ( ArmUtil . isLinuxDynamic ( context . site ) ) { extensionVersion = 'beta' ; } return this . _cacheService . get ( `${ Constants . cdnHost }api/templates?runtime=${ extensionVersion || 'latest' }&cacheBreak=${ window . appsvc . cacheBreakQuery }` , false ) ; } ) . map ( r => { let templates = r . json ( ) as FunctionTemplate [ ] ; if ( ArmUtil . isLinuxApp ( context . site ) ) { templates = templates . filter ( template => ! template . metadata . extensions || template . metadata . extensions . length === 0 ) ; } this . localize ( templates ) ; return templates ; } ) ) ; } statusCodeToText ( code ) { const statusClass = Math . floor ( code / 100 ) * 100 ; return HttpConstants . statusCodeMap [ code ] || HttpConstants . genericStatusCodeMap [ statusClass ] || 'Unknown Status Code' ; } runHttpFunction ( context , functionInfo , url , model , key ? ) { return this . runtime . executeWithConditions ( [ ] , { resourceId : context . site . id } , token => { const content = model . body ; const regExp = /\{([^}]+)\}/g ; const matchesPathParams = url . match ( regExp ) ; const processedParams = [ ] ; const splitResults = url . split ( '?' ) ; if ( splitResults . length === 2 ) { url = splitResults [ 0 ] ; } if ( matchesPathParams ) { matchesPathParams . forEach ( m => { const name = m . split ( ':' ) [ 0 ] . replace ( '{' , '' ) . replace ( '}' , '' ) ; processedParams . push ( name ) ; const param = model . queryStringParams . find ( p => { return p . name === name ; } ) ; if ( param ) { url = url . replace ( m , param . value ) ; } } ) ; } let queryString = '' ; if ( key ) { queryString = `?${ key . name }=${ key . value }` ; } model . queryStringParams . forEach ( p => { const findResult = processedParams . find ( pr => { return pr === p . name ; } ) ; if ( ! findResult ) { if ( ! queryString ) { queryString += '?' ; } else { queryString += '&' ; } queryString += p . name + '=' + p . value ; } } ) ; url = url + queryString ; const inputBinding = functionInfo . config && functionInfo . config . bindings ? functionInfo . config . bindings . find ( e => e . type === 'httpTrigger' ) : null ; let contentType ; if ( ! inputBinding || ( inputBinding && inputBinding . webHookType ) ) { contentType = 'application/json' ; } const headers = this . headers ( token ) ; if ( contentType ) { headers . append ( 'Content-Type' , contentType ) ; } model . headers . forEach ( h => { headers . append ( h . name , h . value ) ; } ) ; if ( ! headers . get ( 'Content-Type' ) ) { headers . append ( 'Content-Type' , 'application/json' ) ; } let response ; switch ( model . method ) { case HttpMethods . GET : response = this . _cacheService . get ( url , true , headers ) ; break ; case HttpMethods . POST : response = this . _cacheService . post ( url , true , headers , content ) ; break ; case HttpMethods . DELETE : response = this . _cacheService . delete ( url , headers ) ; break ; case HttpMethods . HEAD : response = this . _cacheService . head ( url , true , headers ) ; break ; case HttpMethods . PATCH : response = this . _cacheService . patch ( url , headers , content ) ; break ; case HttpMethods . PUT : response = this . _cacheService . put ( url , headers , content ) ; break ; default : response = this . _cacheService . send ( url , model . method , true , headers , content ) ; break ; } return this . runFunctionInternal ( context , response , functionInfo ) ; } ) ; } runFunction ( context , functionInfo , content ) { const url = context . urlTemplates . getRunFunctionUrl ( functionInfo . name . toLocaleLowerCase ( ) ) ; const _content = JSON . stringify ( { input : content } ) ; let contentType ; try { JSON . parse ( _content ) ; contentType = 'application/json' ; } catch ( e ) { contentType = 'plain/text' ; } return this . runtime . executeWithConditions ( [ ] , { resourceId : context . site . id } , t => this . runFunctionInternal ( context , this . _cacheService . post ( url , true , this . headers ( t , [ 'Content-Type' , contentType ] ) , _content ) , functionInfo ) ) ; } updateDisabledAppSettings ( context , infos ) { if ( infos . length > 0 ) { return this . _siteService . getAppSettings ( context . site . id , true ) . switchMap ( r => { const appSettings = r . result ; let needToUpdate = false ; infos . forEach ( info => { const appSettingName = `AzureWebJobs.${ info . name }.Disabled` ; if ( info . config . disabled ) { appSettings . properties [ appSettingName ] = 'true' ; needToUpdate = true ; } else if ( appSettings . properties [ appSettingName ] ) { delete appSettings . properties [ appSettingName ] ; needToUpdate = true ; } } ) ; return needToUpdate ? this . _siteService . updateAppSettings ( context . site . id , appSettings ) : Observable . of ( null ) ; } ) ; } else { return Observable . of ( null ) ; } } getHostV1Json ( context ) { return this . getClient ( context ) . execute ( { resourceId : context . site . id } , t => this . _cacheService . get ( context . urlTemplates . hostJsonUrl , false , this . headers ( t ) ) . map ( r => r . json ( ) ) ) ; } getHostV2V3Json ( context ) { return this . getClient ( context ) . execute ( { resourceId : context . site . id } , t => this . _cacheService . get ( context . urlTemplates . hostJsonUrl , false , this . headers ( t ) ) . map ( r => r . json ( ) ) ) ; } getBindingConfig ( context ) { try { if ( localStorage . getItem ( 'dev-bindings' ) ) { const devBindings = JSON . parse ( localStorage . getItem ( 'dev-bindings' ) ) ; this . localize ( devBindings ) ; return Observable . of ( { isSuccessful : true , result : devBindings , error : null , } ) ; } } catch ( e ) { console . error ( e ) ; } return this . getClient ( context ) . execute ( { resourceId : context . site . id } , t => this . getExtensionVersionFromAppSettings ( context ) . concatMap ( extensionVersion => { if ( ! extensionVersion ) { extensionVersion = 'latest' ; } return this . _cacheService . get ( `${ Constants . cdnHost }api/bindingconfig?runtime=${ extensionVersion }&cacheBreak=${ window . appsvc . cacheBreakQuery }` , false ) ; } ) . map ( r => { const bindingConfig = r . json ( ) as BindingConfig ; if ( ArmUtil . isLinuxApp ( context . site ) ) { const filteredBindings = bindingConfig . bindings . filter ( binding => ! binding . extension ) ; bindingConfig . bindings = filteredBindings ; } this . localize ( bindingConfig ) ; return bindingConfig ; } ) ) ; } getFunctionErrors ( context , fi , handleUnauthorized ? ) { return this . runtime . execute ( { resourceId : context . site . id } , t => this . _cacheService . get ( context . urlTemplates . getFunctionRuntimeErrorsUrl ( fi . name ) , false , this . headers ( t ) ) . map ( r => ( r . json ( ) . errors || [ ] ) as string [ ] ) ) ; } getHostErrors ( context ) { return this . runtime . execute ( { resourceId : context . site . id } , t => this . _cacheService . get ( context . urlTemplates . runtimeStatusUrl , true , this . headers ( t ) ) . map ( r => ( r . json ( ) . errors || [ ] ) as string [ ] ) ) ; } getFunctionHostStatus ( context ) { return this . runtime . execute ( { resourceId : context . site . id } , t => this . _cacheService . get ( context . urlTemplates . runtimeStatusUrl , true , this . headers ( t ) ) . map ( r => r . json ( ) as HostStatus ) ) ; } getWorkerRuntimeRequired ( context ) { return this . getFunctionHostStatus ( context ) . map ( r => { if ( r . isSuccessful ) { const runtimeVersion = new Version ( r . result . version ) ; if ( this . _workerRuntimeRequired ( runtimeVersion ) ) { return true ; } } return false ; } ) ; } private _workerRuntimeRequired ( runtimeVersion ) { if ( runtimeVersion . majorVersion && runtimeVersion . minorVersion ) { return runtimeVersion . majorVersion === 2 && runtimeVersion . minorVersion >= 12050 ; } return false ; } getLogs ( context , functionName , range ? , force = false ) { const url = context . urlTemplates . getFunctionLogUrl ( functionName ) ; return this . getClient ( context ) . execute ( { resourceId : context . site . id } , t => this . _cacheService . get ( url , force , this . headers ( t ) ) . concatMap ( r => { let files = r . json ( ) ; if ( files . length > 0 ) { files = files . map ( e => Object . assign ( { } , e , { parsedTime : new Date ( e . mtime ) } ) ) . sort ( ( a , b ) => a . parsedTime . getTime ( ) - b . parsedTime . getTime ( ) ) ; const headers = range ? this . headers ( t , [ 'Range' , `bytes=-${ range }` ] ) : this . headers ( t ) ; return this . _cacheService . get ( files . pop ( ) . href , force , headers ) . map ( f => { const content = f . text ( ) ; if ( range ) { const index = content . indexOf ( '\n' ) ; return < string > ( index !== - 1 ? content . substring ( index + 1 ) : content ) ; } else { return content ; } } ) ; } else { return Observable . of ( '' ) ; } } ) ) ; } getVfsObjects ( context , fi ) { const href = typeof fi === 'string' ? fi : fi . script_root_path_href ; return this . getClient ( context ) . execute ( { resourceId : context . site . id } , t => this . _cacheService . get ( href , true , this . headers ( t ) ) . map ( e => < VfsObject [ ] > e . json ( ) ) ) ; } createKeyDeprecated ( context , keyName , keyValue , functionInfo ? ) { this . clearKeysCache ( context , functionInfo ) ; const url = functionInfo ? context . urlTemplates . getFunctionKeyUrl ( functionInfo . name , keyName ) : context . urlTemplates . getAdminKeyUrl ( keyName ) ; const body = keyValue ? JSON . stringify ( { name : keyName , value : keyValue , } ) : null ; return this . runtime . execute ( { resourceId : context . site . id } , t => { const req = body ? this . _cacheService . put ( url , this . jsonHeaders ( t ) , body ) : this . _cacheService . post ( url , true , this . jsonHeaders ( t ) ) ; return req . map ( r => r . json ( ) as FunctionKey ) ; } ) ; } renewKey ( context , key , functionInfo ? ) { this . clearKeysCache ( context , functionInfo ) ; const url = functionInfo ? context . urlTemplates . getFunctionKeyUrl ( functionInfo . name , key . name ) : context . urlTemplates . getAdminKeyUrl ( key . name ) ; return this . runtime . execute ( { resourceId : context . site . id } , t => this . _cacheService . post ( url , true , this . jsonHeaders ( t ) ) ) ; } private clearKeysCache ( context , functionInfo ? ) { if ( functionInfo ) { this . _cacheService . clearCachePrefix ( context . urlTemplates . getFunctionKeysUrl ( functionInfo . name ) ) ; } else { this . _cacheService . clearCachePrefix ( context . urlTemplates . adminKeysUrl ) ; this . _cacheService . clearCachePrefix ( context . urlTemplates . systemKeysUrl ) ; } } fireSyncTrigger ( context ) { if ( ArmUtil . isLinuxDynamic ( context . site ) ) { this . _cacheService . postArm ( `${ context . site . id }/hostruntime/admin/host/synctriggers` , true ) . subscribe ( success => this . _logService . verbose ( LogCategories . syncTriggers , success ) , error => this . _logService . error ( LogCategories . syncTriggers , '/sync-triggers-error' , error ) ) ; } else { const url = context . urlTemplates . syncTriggersUrl ; this . azure . execute ( { resourceId : context . site . id } , t => this . _cacheService . post ( url , true , this . jsonHeaders ( t ) ) ) . subscribe ( success => this . _logService . verbose ( LogCategories . syncTriggers , success ) , error => this . _logService . error ( LogCategories . syncTriggers , '/sync-triggers-error' , error ) ) ; } } isSourceControlEnabled ( context ) { return this . azure . executeWithConditions ( [ ] , { resourceId : context . site . id } , this . _cacheService . getArm ( `${ context . site . id }/config/web` , null , ARMApiVersions . antaresApiVersion20181101 ) . map ( r => { const config = r . json ( ) ; return ! config . properties [ 'scmType' ] || config . properties [ 'scmType' ] !== 'None' ; } ) ) ; } isSlotsSupported ( appSettingsArm ) { const appSettings = ! ! appSettingsArm && appSettingsArm . properties ; const [ runtimeVersionAppSetting , secretStorageSetting ] = [ AppSettingsHelper . getAppSettingInfo ( Constants . runtimeVersionAppSettingName , appSettings ) , AppSettingsHelper . getAppSettingInfo ( Constants . secretStorageSettingsName , appSettings ) , ] ; const [ runtimeVersionAppSettingValue , secretStorageSettingValue ] = [ runtimeVersionAppSetting && runtimeVersionAppSetting . value , secretStorageSetting && secretStorageSetting . value && secretStorageSetting . value . toLowerCase ( ) , ] ; const runtimeGeneration = FunctionsVersionInfoHelper . getFunctionGeneration ( runtimeVersionAppSettingValue ) ; let isUsingFilesSecretStorage = true ; if ( runtimeGeneration === 'V1' ) { if ( secretStorageSettingValue === Constants . secretStorageSettingsValueBlob . toLowerCase ( ) ) { isUsingFilesSecretStorage = false ; } else { isUsingFilesSecretStorage = true ; } } else { if ( secretStorageSettingValue === Constants . secretStorageSettingsValueFiles . toLowerCase ( ) ) { isUsingFilesSecretStorage = true ; } else { isUsingFilesSecretStorage = false ; } } return ! isUsingFilesSecretStorage ; } isSlot ( context ) { return ! ! this . getSlotName ( context ) ; } getSlotName ( context ) { const id = typeof context === 'string' ? context : context . site . id ; const descriptor = new ArmSiteDescriptor ( id ) ; return descriptor . slot ; } getSlotsList ( context ) { const id = typeof context === 'string' ? context : context . site . id ; return this . isSlot ( context ) ? Observable . of ( { isSuccessful : true , result : [ ] , error : null , } ) : this . azure . executeWithConditions ( [ ] , typeof context !== 'string' ? { resourceId : context . site . id } : null , this . _cacheService . getArm ( `${ id }/slots` ) . map ( r => r . json ( ) . value as ArmObj < Site > [ ] ) ) ; } reachableInternalLoadBalancerApp ( context ) { if ( context && context . site && context . site . properties . hostingEnvironmentProfile && context . site . properties . hostingEnvironmentProfile . id ) { return this . _cacheService . getArm ( context . site . properties . hostingEnvironmentProfile . id , false , ARMApiVersions . antaresApiVersion20181101 ) . mergeMap ( r => { const ase = r . json ( ) ; if ( ase . properties . internalLoadBalancingMode && ase . properties . internalLoadBalancingMode !== InternalLoadBalancingMode . None ) { return this . pingScmSite ( context ) . map ( result => result . isSuccessful ) ; } else { return Observable . of ( true ) ; } } ) . catch ( ( ) => { return Observable . of ( true ) ; } ) ; } else { return Observable . of ( true ) ; } } getFunctionAppEditMode ( context ) { return this . azure . executeWithConditions ( [ ] , { resourceId : context . site . id } , Observable . zip ( this . isSourceControlEnabled ( context ) , this . _siteService . getAppSettings ( context . site . id ) , this . isSlot ( context ) ? Observable . of ( { isSuccessful : true , result : true , error : null } ) : this . getSlotsList ( context ) . map ( r => ( r . isSuccessful ? Object . assign ( r , { result : r . result . length > 0 } ) : r ) ) , this . _functionService . getFunctions ( context . site . id ) , ( a , b , s , f ) => ( { sourceControlEnabled : a , appSettingsResponse : b , hasSlots : s , functionsInfo : f . isSuccessful ? f . result . value : [ ] , } ) ) . map ( result => { const appSettings = result . appSettingsResponse . isSuccessful ? result . appSettingsResponse . result : null ; const sourceControlled = result . sourceControlEnabled . isSuccessful && result . sourceControlEnabled . result ; let editModeSettingString = appSettings ? appSettings . properties [ Constants . functionAppEditModeSettingName ] || '' : '' ; editModeSettingString = editModeSettingString . toLocaleLowerCase ( ) ; const vsCreatedFunc = ! ! result . functionsInfo . find ( ( fc ) => ! ! fc . properties . config . generatedBy ) ; const usingRunFromZip = appSettings ? this . _getRFZSetting ( appSettings ) !== '0' : false ; const usingLocalCache = appSettings && appSettings . properties [ Constants . localCacheOptionSettingName ] === Constants . localCacheOptionSettingValue ; const hasSlots = result . hasSlots . result ; const isLinuxElastic = ArmUtil . isLinuxElastic ( context . site ) ; const isLinuxDynamic = ArmUtil . isLinuxDynamic ( context . site ) ; const isContainerApp = appSettings && appSettings . properties [ ContainerConstants . appServiceStorageSetting ] === 'false' ; const workerRuntime = appSettings && appSettings . properties [ Constants . functionsWorkerRuntimeAppSettingsName ] ; const isPython = workerRuntime && WorkerRuntimeLanguages [ workerRuntime ] === WorkerRuntimeLanguages . python ; const isJava = workerRuntime && WorkerRuntimeLanguages [ workerRuntime ] === WorkerRuntimeLanguages . java ; const resolveReadOnlyMode = ( ) => { if ( sourceControlled ) { return FunctionAppEditMode . ReadOnlySourceControlled ; } else if ( vsCreatedFunc ) { return FunctionAppEditMode . ReadOnlyVSGenerated ; } else if ( hasSlots ) { return FunctionAppEditMode . ReadOnly ; } else { return FunctionAppEditMode . ReadOnly ; } } ; const resolveReadWriteMode = ( ) => { if ( sourceControlled ) { return FunctionAppEditMode . ReadWriteSourceControlled ; } else if ( vsCreatedFunc ) { return FunctionAppEditMode . ReadWriteVSGenerated ; } else if ( hasSlots ) { return FunctionAppEditMode . ReadWrite ; } else { return FunctionAppEditMode . ReadWrite ; } } ; const resolveUndefined = ( ) => { if ( sourceControlled ) { return FunctionAppEditMode . ReadOnlySourceControlled ; } else if ( vsCreatedFunc ) { return FunctionAppEditMode . ReadOnlyVSGenerated ; } else if ( hasSlots ) { return FunctionAppEditMode . ReadOnlySlots ; } else { return FunctionAppEditMode . ReadWrite ; } } ; if ( usingRunFromZip ) { return FunctionAppEditMode . ReadOnlyRunFromZip ; } else if ( usingLocalCache ) { return FunctionAppEditMode . ReadOnlyLocalCache ; } else if ( isPython ) { return FunctionAppEditMode . ReadOnlyPython ; } else if ( isJava ) { return FunctionAppEditMode . ReadOnlyJava ; } else if ( isLinuxDynamic ) { return FunctionAppEditMode . ReadOnlyLinuxDynamic ; } else if ( isContainerApp ) { return FunctionAppEditMode . ReadOnlyBYOC ; } else if ( isLinuxElastic ) { return FunctionAppEditMode . ReadOnlyLinuxCodeElastic ; } else if ( editModeSettingString === Constants . ReadWriteMode ) { return resolveReadWriteMode ( ) ; } else if ( editModeSettingString === Constants . ReadOnlyMode ) { return resolveReadOnlyMode ( ) ; } else { return resolveUndefined ( ) ; } } ) . catch ( ( ) => Observable . of ( FunctionAppEditMode . ReadWrite ) ) ) ; } private _getRFZSetting ( appSettings ) { return ( appSettings . properties [ Constants . WebsiteUseZip ] || appSettings . properties [ Constants . WebsiteRunFromZip ] || appSettings . properties [ Constants . WebsiteRunFromPackage ] || '0' ) ; } public getAuthSettings ( context ) { return this . azure . executeWithConditions ( [ ] , { resourceId : context . site . id } , this . _cacheService . postArm ( `${ context . site . id }/config/authsettings/list` ) . map ( r => { const auth = r . json ( ) ; return { easyAuthEnabled : auth . properties [ 'enabled' ] && auth . properties [ 'unauthenticatedClientAction' ] !== 1 , AADConfigured : auth . properties [ 'clientId' ] || false , AADNotConfigured : auth . properties [ 'clientId' ] ? false : true , clientCertEnabled : context . site . properties . clientCertEnabled , } ; } ) ) ; } /**
     * This method just pings the root of the SCM site. It doesn't care about the response in anyway or use it.
     */ pingScmSite ( context ) { if ( ArmUtil . isLinuxDynamic ( context . site ) ) { return Observable . of ( { isSuccessful : true , result : true , error : null , } ) ; } return this . azure . execute ( { resourceId : context . site . id } , t => this . _cacheService . get ( context . urlTemplates . pingScmSiteUrl , true , this . headers ( t ) ) . map ( _ => true ) . catch ( ( ) => Observable . of ( false ) ) ) ; } private runFunctionInternal ( context , response , functionInfo ) { return response . catch ( ( e ) => { return this . getAuthSettings ( context ) . map ( r => ( r . isSuccessful ? r . result : { easyAuthEnabled : false , clientCertEnabled : false } ) ) . mergeMap ( authSettings => { if ( authSettings . easyAuthEnabled ) { return Observable . of ( { status : 401 , statusText : this . statusCodeToText ( 401 ) , text : ( ) => this . _translateService . instant ( PortalResources . functionService_authIsEnabled ) , } ) ; } else if ( authSettings . clientCertEnabled ) { return Observable . of ( { status : 401 , statusText : this . statusCodeToText ( 401 ) , text : ( ) => this . _translateService . instant ( PortalResources . functionService_clientCertEnabled ) , } ) ; } else if ( e . status === 200 && e . type === ResponseType . Error ) { return Observable . of ( { status : 502 , statusText : this . statusCodeToText ( 502 ) , text : ( ) => this . _translateService . instant ( PortalResources . functionService_errorRunningFunc , { name : functionInfo . name , } ) , } ) ; } else if ( e . status === 0 && e . type === ResponseType . Error ) { return Observable . of ( { status : 0 , statusText : this . statusCodeToText ( 0 ) , text : ( ) => '' , } ) ; } else { let text = '' ; try { text = JSON . stringify ( e . json ( ) , undefined , 2 ) ; } catch ( ex ) { text = e . text ( ) ; } return Observable . of ( { status : e . status , statusText : this . statusCodeToText ( e . status ) , text : ( ) => text , } ) ; } } ) ; } ) . map ( r => < RunFunctionResult > { statusCode : r . status , statusText : this . statusCodeToText ( r . status ) , content : r . text ( ) } ) ; } getGeneratedSwaggerData ( context , key ) { const url = context . urlTemplates . getGeneratedSwaggerDataUrl ; return this . runtime . execute ( { resourceId : context . site . id } , t => this . _cacheService . get ( `${ url }?code=${ key }` , false , this . headers ( t ) ) . map ( r => r . json ( ) ) ) ; } getSwaggerDocument ( context , key ) { const url = context . urlTemplates . getSwaggerDocumentUrl ; return this . runtime . execute ( { resourceId : context . site . id } , t => this . _cacheService . get ( `${ url }?code=${ key }` , false , this . headers ( t ) ) . map ( r => r . json ( ) ) ) ; } addOrUpdateSwaggerDocument ( context , swaggerUrl , content ) { return this . runtime . execute ( { resourceId : context . site . id } , this . _cacheService . post ( swaggerUrl , false , this . jsonHeaders ( null ) , content ) . map ( r => r . json ( ) ) ) ; } deleteSwaggerDocument ( context , swaggerUrl ) { return this . runtime . execute ( { resourceId : context . site . id } , this . _cacheService . delete ( swaggerUrl ) ) ; } saveHostJson ( context , jsonString ) { return this . getClient ( context ) . execute ( { resourceId : context . site . id } , t => this . _cacheService . put ( context . urlTemplates . hostJsonUrl , this . jsonHeaders ( t , [ 'If-Match' , '*' ] ) , jsonString ) . map ( r => r . json ( ) ) ) ; } createSystemKey ( context , keyName ) { return this . runtime . execute ( { resourceId : context . site . id } , t => this . _cacheService . post ( context . urlTemplates . getSystemKeyUrl ( keyName ) , true , this . jsonHeaders ( t , [ 'If-Match' , '*' ] ) ) . map ( r => r . json ( ) ) ) ; } getHostExtensions ( context ) { return this . runtime . execute ( { resourceId : context . site . id } , t => this . _cacheService . get ( context . urlTemplates . runtimeHostExtensionsUrl , true , this . headers ( t ) ) . map ( r => r . json ( ) as FunctionKeys ) ) ; } installExtension ( context , extension ) { const requestBody = { ... extension , PostInstallActions : 'BringAppOnline' } ; return this . runtime . execute ( { resourceId : context . site . id } , t => this . _cacheService . post ( context . urlTemplates . runtimeHostExtensionsUrl , true , this . jsonHeaders ( t ) , requestBody ) . map ( r => r . json ( ) as ExtensionInstallStatus ) ) ; } uninstallExtension ( context , extensionId ) { return this . runtime . execute ( { resourceId : context . site . id } , t => this . _cacheService . delete ( context . urlTemplates . getRuntimeHostExtensionsIdUrl ( extensionId ) , this . headers ( t ) ) . map ( r => r . json ( ) as ExtensionInstallStatus ) ) ; } getExtensionInstallStatus ( context , jobId ) { return this . runtime . execute ( { resourceId : context . site . id } , t => this . _cacheService . get ( context . urlTemplates . getRuntimeHostExtensionsJobStatusUrl ( jobId ) , true , this . headers ( t ) ) . map ( r => r . json ( ) as ExtensionInstallStatus ) ) ; } getExtensionJobsStatus ( context ) { return this . runtime . execute ( { resourceId : context . site . id } , t => this . _cacheService . get ( context . urlTemplates . runtimeHostExtensionsJobsUrl , true , this . headers ( t ) ) . map ( r => r . json ( ) as ExtensionJobsStatus ) ) ; } getExtensionJson ( context ) { return this . azure . execute ( { resourceId : context . site . id } , t => this . _cacheService . get ( context . urlTemplates . extensionJsonUrl , true , this . headers ( t ) ) . map ( r => r . json ( ) as ExtensionsJson ) . map ( r => r . extensions ) ) ; } updateHostState ( context , stateValue ) { return this . runtime . execute ( { resourceId : context . site . id } , t => this . _cacheService . put ( context . urlTemplates . updateHostStateUrl , this . jsonHeaders ( t ) , `'${ stateValue }'` ) ) ; } getSystemKey ( context ) { return this . runtime . execute ( { resourceId : context . site . id } , t => this . _cacheService . get ( context . urlTemplates . systemKeysUrl , false , this . headers ( t ) ) . map ( r => r . json ( ) as FunctionKeys ) ) ; } getEventGridUri ( context , functionName ) { return Observable . zip ( this . getSystemKey ( context ) , this . getRuntimeGeneration ( context ) ) . map ( tuple => { if ( tuple [ 0 ] . isSuccessful ) { const generation = tuple [ 1 ] ; const eventGridName = runtimeIsV1 ( generation ) ? Constants . eventGridName_v1 : Constants . eventGridName_v2 ; const key = tuple [ 0 ] . result . keys . find ( k => k . name === eventGridName ) ; return { isSuccessful : true , result : key ? FunctionsVersionInfoHelper . getEventGridUri ( generation , context . mainSiteUrl , functionName , key . value ) : '' , error : null , } ; } else { return ( tuple [ 0 ] as any ) as HttpResult < string > ; } } ) ; } createAuthSettings ( context , newAuthSettings ) { if ( newAuthSettings . size > 0 ) { return this . _cacheService . postArm ( `${ context . site . id }/config/authsettings/list` , true ) . flatMap ( r => { const authSettings = r . json ( ) ; newAuthSettings . forEach ( ( value , key ) => { authSettings . properties [ key ] = value ; } ) ; return this . _cacheService . putArm ( authSettings . id , null , authSettings ) ; } ) ; } else { return Observable . of ( null ) ; } } restartFunctionsHost ( context ) { return this . runtime . execute ( { resourceId : context . site . id } , t => this . _cacheService . post ( context . urlTemplates . restartHostUrl , true , this . headers ( t ) ) ) ; } getAppContext ( resourceId , force ? ) { return this . _cacheService . getArm ( resourceId , force ) . map ( r => ArmUtil . mapArmSiteToContext ( r . json ( ) , this . _injector ) ) . switchMap ( context => { return Observable . zip ( Observable . of ( context ) , this . getRuntimeGeneration ( context ) ) ; } ) . switchMap ( tuple => { const newContext = tuple [ 0 ] ; const version = tuple [ 1 ] ; newContext . urlTemplates . runtimeVersion = version ; return Observable . of ( newContext ) ; } ) ; } getAppContentAsZip ( context , includeCsProj , includeAppSettings ) { const url = ArmUtil . isLinuxApp ( context . site ) ? `${ context . mainSiteUrl }/admin/functions/download?includeCsproj=${ includeCsProj }&includeAppSettings=${ includeAppSettings }` : `${ context . scmUrl }/api/functions/admin/download?includeCsproj=${ includeCsProj }&includeAppSettings=${ includeAppSettings }` ; const client = ArmUtil . isLinuxApp ( context . site ) ? this . runtime : this . azure ; return client . execute ( { resourceId : context . site . id } , t => this . _cacheService . get ( url , true , this . headers ( t ) , null , ResponseContentType . Blob ) . map ( r => new Blob ( [ r . blob ( ) ] , { type : 'application/octet-stream' } ) ) ) ; } getFunctionTestData ( context , testDataHref ) { return this . getClient ( context ) . execute ( { resourceId : context . site . id } , t => { return this . _cacheService . get ( testDataHref , true , this . headers ( t ) ) . map ( r => r . text ( ) ) ; } ) ; } _tryFunctionsBasicAuthToken : string ; setTryFunctionsToken ( token ) { this . _tryFunctionsBasicAuthToken = token ; } private localize ( objectToLocalize ) { if ( typeof objectToLocalize === 'string' && objectToLocalize . startsWith ( '$' ) ) { const key = objectToLocalize . substring ( 1 , objectToLocalize . length ) ; objectToLocalize = this . _translateService . instant ( key ) ; } else if ( Array . isArray ( objectToLocalize ) ) { for ( let i = 0 ; i < objectToLocalize . length ; i ++ ) { objectToLocalize [ i ] = this . localize ( objectToLocalize [ i ] ) ; } } else if ( typeof objectToLocalize === 'object' ) { for ( const property in objectToLocalize ) { if ( property === 'files' || property === 'defaultValue' || property === 'function' ) { continue ; } if ( objectToLocalize . hasOwnProperty ( property ) ) { objectToLocalize [ property ] = this . localize ( objectToLocalize [ property ] ) ; } } } return objectToLocalize ; } private portalHeaders ( authToken , ... aditionalHeaders ) { const headers = aditionalHeaders . slice ( ) ; headers . unshift ( [ 'portal-token' , authToken ] ) ; return this . jsonHeaders . apply ( this , headers ) ; } private jsonHeaders ( authTokenOrHeader , ... additionalHeaders ) { const headers = additionalHeaders . slice ( ) ; headers . unshift ( [ 'Content-Type' , 'application/json' ] ) ; if ( authTokenOrHeader ) { headers . unshift ( authTokenOrHeader ) ; } return this . headers . apply ( this , headers ) ; } private headers ( authTokenOrHeader , ... additionalHeaders ) { const headers = new Headers ( ) ; if ( typeof authTokenOrHeader === 'string' && authTokenOrHeader . length > 0 ) { if ( authTokenOrHeader . startsWith ( 'masterKey ' ) ) { headers . set ( 'Cache-Control' , 'no-cache' ) ; headers . set ( 'x-functions-key' , authTokenOrHeader . substring ( 'masterKey ' . length ) ) ; } else { headers . set ( 'Authorization' , `Bearer ${ authTokenOrHeader }` ) ; } } else if ( this . _tryFunctionsBasicAuthToken ) { headers . set ( 'Authorization' , `Basic ${ this . _tryFunctionsBasicAuthToken }` ) ; } if ( Array . isArray ( authTokenOrHeader ) ) { headers . set ( authTokenOrHeader [ 0 ] , authTokenOrHeader [ 1 ] ) ; } additionalHeaders . forEach ( header => { headers . set ( header [ 0 ] , header [ 1 ] ) ; } ) ; return headers ; } } param file