import { StorageManagementClient } from "@azure/arm-storage" ; import { Kind , SkuName , StorageAccount , StorageAccountsCreateResponse , StorageAccountsListKeysResponse , } from "@azure/arm-storage/esm/models" ; import * as storage from "azure-storage" ; import { exec } from "child_process" ; import { promisify } from "util" ; import { Config } from "../../config" ; import { logger } from "../../logger" ; import { AzureAccessOpts } from "../../types" ; import { getManagementCredentials } from "./azurecredentials" ; import { build as buildError } from "../../lib/errorBuilder" ; import { errorStatusCode } from "../../lib/errorStatusCode" ; import { hasValue } from "../validator" ; let storageManagementClient ; param opts export const getStorageManagementClient = async ( opts = { } ) : Promise < StorageManagementClient > => { if ( storageManagementClient !== undefined ) { logger . debug ( `Returing singleton storageManagementClient object` ) ; return storageManagementClient ; } const creds = await getManagementCredentials ( opts ) ; if ( ! creds ) { throw buildError ( errorStatusCode . AZURE_STORAGE_OP_ERR , "storage-client-err-missing-creds" ) ; } const introspection = Config ( ) . introspection ; const azure = introspection ? introspection . azure : undefined ; const { subscriptionId = azure && azure . subscription_id } = opts ; if ( ! subscriptionId ) { throw buildError ( errorStatusCode . AZURE_STORAGE_OP_ERR , "storage-client-err-missing-sub-id" ) ; } storageManagementClient = new StorageManagementClient ( creds , subscriptionId ) ; logger . debug ( `created storageManagementClient object` ) ; return storageManagementClient ; } ; param accountName export const isStorageAccountNameAvailable = async ( accountName ) : Promise < boolean > => { logger . verbose ( `Check if storage account name ${ accountName } exists` ) ; const client = await getStorageManagementClient ( ) ; const nameAvailabilityResult = await client . storageAccounts . checkNameAvailability ( accountName ) ; return nameAvailabilityResult . nameAvailable || false ; } ; param accountName param resourceGroup param opts export const getStorageAccountKeys = async ( accountName , resourceGroup , opts = { } ) : Promise < string [ ] > => { if ( ! hasValue ( resourceGroup ) || ! hasValue ( accountName ) ) { throw buildError ( errorStatusCode . AZURE_STORAGE_OP_ERR , "storage-account-keys-err-missing-vals" ) ; } const storageAccountKeys = [ ] ; logger . verbose ( `Get storage account keys for ${ accountName }` ) ; const client = await getStorageManagementClient ( opts ) ; const keysResponse = await client . storageAccounts . listKeys ( resourceGroup , accountName ) ; if ( keysResponse . keys ) { for ( const storageKey of keysResponse . keys ) { if ( storageKey . value ) { storageAccountKeys . push ( storageKey . value ) ; } } } return storageAccountKeys ; } ; param resourceGroup param accountName param key export const validateStorageAccount = async ( resourceGroup , accountName , key ) : Promise < boolean > => { try { logger . info ( `Validating storage account ${ accountName }.` ) ; const isNameAvailable = await isStorageAccountNameAvailable ( accountName ) ; if ( isNameAvailable ) { logger . error ( `Storage account ${ accountName } does not exist.` ) ; return false ; } const storageAccountKeys = await getStorageAccountKeys ( accountName , resourceGroup ) ; const found = ( storageAccountKeys || [ ] ) . find ( ( k ) => k === key ) ; if ( found ) { logger . info ( `Storage account validation for ${ accountName } succeeded.` ) ; return true ; } logger . error ( `Storage account ${ accountName } access keys is not valid or does not exist.` ) ; return false ; } catch ( err ) { throw buildError ( errorStatusCode . AZURE_STORAGE_OP_ERR , { errorKey : "storage-account-validate-azure-err" , values : [ accountName , resourceGroup ] , } , err ) ; } } ; param resourceGroup param accountName param opts export const getStorageAccount = async ( resourceGroup , accountName , opts = { } ) : Promise < StorageAccount | undefined > => { if ( ! hasValue ( resourceGroup ) || ! hasValue ( accountName ) ) { throw buildError ( errorStatusCode . AZURE_STORAGE_OP_ERR , "storage-account-get-err-missing-vals" ) ; } const message = `Azure storage account ${ accountName } in resource group ${ resourceGroup }` ; try { logger . debug ( `Finding ${ message }` ) ; const client = await getStorageManagementClient ( opts ) ; const accounts = await client . storageAccounts . listByResourceGroup ( resourceGroup ) ; if ( accounts === undefined || accounts === null || accounts . length === 0 ) { logger . debug ( `No storage accounts found in ${ resourceGroup }` ) ; return undefined ; } logger . debug ( `${ accounts . length } storage accounts found in ${ resourceGroup }` ) ; const found = accounts . find ( ( acc ) => acc . name === accountName ) ; if ( found ) { logger . debug ( `Found ${ message }` ) ; } return found ; } catch ( err ) { throw buildError ( errorStatusCode . AZURE_STORAGE_OP_ERR , { errorKey : "storage-account-get-azure-err" , values : [ accountName , resourceGroup ] , } , err ) ; } } ; param resourceGroup param accountName param opts export const isStorageAccountExist = async ( resourceGroup , accountName , opts = { } ) : Promise < boolean > => { if ( ! hasValue ( resourceGroup ) || ! hasValue ( accountName ) ) { throw buildError ( errorStatusCode . AZURE_STORAGE_OP_ERR , "storage-account-exist-err-missing-vals" ) ; } try { const account = await getStorageAccount ( resourceGroup , accountName , opts ) ; return account !== undefined ; } catch ( err ) { throw buildError ( errorStatusCode . AZURE_STORAGE_OP_ERR , { errorKey : "storage-account-exist-azure-err" , values : [ accountName , resourceGroup ] , } , err ) ; } } ; param resourceGroup param accountName param location const validateInputsForCreateAccount = ( resourceGroup , accountName , location ) : void => { if ( ! hasValue ( resourceGroup ) || ! hasValue ( accountName ) || ! hasValue ( location ) ) { throw buildError ( errorStatusCode . AZURE_STORAGE_OP_ERR , "storage-account-create-err-missing-vals" ) ; } } ; param resourceGroup param accountName param location param opts export const createStorageAccount = async ( resourceGroup , accountName , location , opts = { } ) : Promise < StorageAccount > => { const message = `Azure storage account ${ accountName } in resource group ${ resourceGroup } in ${ location } location.` ; validateInputsForCreateAccount ( resourceGroup , accountName , location ) ; try { logger . verbose ( `Create storage client object.` ) ; const client = await getStorageManagementClient ( opts ) ; logger . verbose ( `Checking for storage account name ${ accountName } availability` ) ; const response = await client . storageAccounts . checkNameAvailability ( accountName ) ; if ( response . nameAvailable === false ) { throw buildError ( errorStatusCode . AZURE_STORAGE_OP_ERR , { errorKey : "storage-account-create-err-name-taken" , values : [ accountName ] , } ) ; } logger . verbose ( `Storage account name ${ accountName } is available` ) ; const createParameters = { kind : "Storage" as Kind , location , sku : { name : "Standard_LRS" as SkuName , } , } ; logger . info ( `Creating ${ message }` ) ; const resp = await client . storageAccounts . create ( resourceGroup , accountName , createParameters ) ; logger . info ( `Created ${ message }` ) ; return resp ; } catch ( err ) { throw buildError ( errorStatusCode . AZURE_STORAGE_OP_ERR , { errorKey : "storage-account-create-azure-err" , values : [ accountName ] , } , err ) ; } } ; param resourceGroup param accountName param opts returns export const getStorageAccountKey = async ( resourceGroup , accountName , opts = { } ) : Promise < string | undefined > => { if ( ! hasValue ( resourceGroup ) || ! hasValue ( accountName ) ) { throw buildError ( errorStatusCode . AZURE_STORAGE_OP_ERR , "storage-account-key-missing-vals" ) ; } try { const client = await getStorageManagementClient ( opts ) ; logger . verbose ( `Reading storage account ${ accountName } access keys` ) ; const keyResults = await client . storageAccounts . listKeys ( resourceGroup , accountName ) ; if ( keyResults . keys === undefined || keyResults . keys . length === 0 ) { logger . verbose ( `Storage account ${ accountName } access keys do not exist` ) ; return undefined ; } logger . verbose ( `${ keyResults . keys . length } Storage account access keys exist` ) ; const key = keyResults . keys [ 0 ] . value ; logger . verbose ( `Returning key: ${ key }` ) ; return key ; } catch ( err ) { throw buildError ( errorStatusCode . AZURE_STORAGE_OP_ERR , "storage-account-key-azure-err" , err ) ; } } ; param accountName param tableName param accessKey const validateValuesForCreateStorageTable = ( accountName , tableName ) : void => { if ( ! hasValue ( accountName ) || ! hasValue ( tableName ) ) { throw buildError ( errorStatusCode . AZURE_STORAGE_OP_ERR , "storage-account-table-create-missing-vals" ) ; } } ; param accountName param tableName param accessKey export const createTableIfNotExists = ( accountName , tableName , accessKey ) : Promise < boolean > => { return new Promise < boolean > ( ( resolve , reject ) => { try { validateValuesForCreateStorageTable ( accountName , tableName ) ; const createTblService = storage . createTableService ( accountName , accessKey ) ; createTblService . createTableIfNotExists ( tableName , ( err , result ) => { if ( err ) { reject ( buildError ( errorStatusCode . AZURE_STORAGE_OP_ERR , "storage-account-table-create-err" , err ) ) ; } else { logger . debug ( `table result: ${ JSON . stringify ( result ) }` ) ; resolve ( result . created ) ; } } ) ; } catch ( err ) { reject ( err ) ; } } ) ; } ; param name param location export const createResourceGroupIfNotExists = async ( name , location ) : Promise < void > => { if ( ! hasValue ( name ) || ! hasValue ( location ) ) { throw buildError ( errorStatusCode . AZURE_RESOURCE_GROUP_ERR , "resource-group-create-err-missing-vals" ) ; } const message = `Azure resource group ${ name } in ${ location } location` ; try { logger . info ( `Checking weather resource group ${ name } exists in ${ location } location.` ) ; const response = await promisify ( exec ) ( `az group exists -n ${ name }` ) ; if ( response . stdout === "true" ) { logger . info ( `${ message } already exists.` ) ; } else { logger . info ( `Creating ${ message }` ) ; await promisify ( exec ) ( `az group create -n ${ name } -l ${ location }` ) ; logger . info ( `Created ${ message }` ) ; } } catch ( err ) { throw buildError ( errorStatusCode . AZURE_RESOURCE_GROUP_ERR , "resource-group-create-err" , err ) ; } } ;