From b8fc003a4cca17f6cb1d7de11b43548a8fb64bde Mon Sep 17 00:00:00 2001 From: Piwccle Date: Tue, 13 May 2025 11:01:22 +0200 Subject: [PATCH] openvidu-deployment: azure - added existing storage account support in CE, Elastic and HA deployments --- .../azure/cf-openvidu-singlenode.bicep | 11 ++- .../azure/cf-openvidu-singlenode.json | 4 +- .../elastic/azure/cf-openvidu-elastic.bicep | 31 ++++++--- .../elastic/azure/cf-openvidu-elastic.json | 23 +++++-- .../pro/ha/azure/cf-openvidu-ha.bicep | 69 ++++++++++++++++--- .../pro/ha/azure/cf-openvidu-ha.json | 44 ++++++------ 6 files changed, 131 insertions(+), 51 deletions(-) diff --git a/openvidu-deployment/community/singlenode/azure/cf-openvidu-singlenode.bicep b/openvidu-deployment/community/singlenode/azure/cf-openvidu-singlenode.bicep index 96fec22d..bf9f8e71 100644 --- a/openvidu-deployment/community/singlenode/azure/cf-openvidu-singlenode.bicep +++ b/openvidu-deployment/community/singlenode/azure/cf-openvidu-singlenode.bicep @@ -728,7 +728,7 @@ var store_secretScript = reduce( ).value var blobStorageParams = { - storageAccountName: storageAccount.name + storageAccountName: isEmptyStorageAccountName ? storageAccount.name : exisitngStorageAccount.name storageAccountKey: listKeys(storageAccount.id, '2021-04-01').keys[0].value storageAccountContainerName: isEmptyContainerName ? 'openvidu-appdata' : '${containerName}' } @@ -1100,6 +1100,11 @@ resource webServerSecurityGroup 'Microsoft.Network/networkSecurityGroups@2023-11 /*------------------------------------------- STORAGE ACCOUNT ----------------------------------------*/ +@description('Name of an existing storage account. It is essential that this parameter is filled just when you want to save recordings and still using the same container after an update. If not specified, a new storage account will be generated.') +param storageAccountName string = '' + +var isEmptyStorageAccountName = storageAccountName == '' + @description('Name of the bucket where OpenVidu will store the recordings. If not specified, a default bucket will be created.') param containerName string = '' @@ -1116,6 +1121,10 @@ resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = { } } +resource exisitngStorageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' existing = if (isEmptyStorageAccountName == false) { + name: storageAccountName +} + var isEmptyContainerName = containerName == '' resource blobContainer 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = { diff --git a/openvidu-deployment/community/singlenode/azure/cf-openvidu-singlenode.json b/openvidu-deployment/community/singlenode/azure/cf-openvidu-singlenode.json index 56407035..de95acbe 100644 --- a/openvidu-deployment/community/singlenode/azure/cf-openvidu-singlenode.json +++ b/openvidu-deployment/community/singlenode/azure/cf-openvidu-singlenode.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.34.44.8038", - "templateHash": "6604641218613788020" + "version": "0.35.1.17967", + "templateHash": "17646090837985029822" } }, "parameters": { diff --git a/openvidu-deployment/pro/elastic/azure/cf-openvidu-elastic.bicep b/openvidu-deployment/pro/elastic/azure/cf-openvidu-elastic.bicep index c5e5e1c2..7d7538dd 100644 --- a/openvidu-deployment/pro/elastic/azure/cf-openvidu-elastic.bicep +++ b/openvidu-deployment/pro/elastic/azure/cf-openvidu-elastic.bicep @@ -890,7 +890,7 @@ set -e # Install dir and config dir INSTALL_DIR="/opt/openvidu" -CONFIG_DIR="${INSTALL_DIR}/config" +CLUSTER_CONFIG_DIR="${INSTALL_DIR}/config/cluster" az login --identity @@ -899,9 +899,9 @@ AZURE_ACCOUNT_NAME="${storageAccountName}" AZURE_ACCOUNT_KEY=$(az storage account keys list --account-name ${storageAccountName} --query '[0].value' -o tsv) AZURE_CONTAINER_NAME="${storageAccountContainerName}" -sed -i "s|AZURE_ACCOUNT_NAME=.*|AZURE_ACCOUNT_NAME=$AZURE_ACCOUNT_NAME|" "${CONFIG_DIR}/openvidu.env" -sed -i "s|AZURE_ACCOUNT_KEY=.*|AZURE_ACCOUNT_KEY=$AZURE_ACCOUNT_KEY|" "${CONFIG_DIR}/openvidu.env" -sed -i "s|AZURE_CONTAINER_NAME=.*|AZURE_CONTAINER_NAME=$AZURE_CONTAINER_NAME|" "${CONFIG_DIR}/openvidu.env" +sed -i "s|AZURE_ACCOUNT_NAME=.*|AZURE_ACCOUNT_NAME=$AZURE_ACCOUNT_NAME|" "${CLUSTER_CONFIG_DIR}/openvidu.env" +sed -i "s|AZURE_ACCOUNT_KEY=.*|AZURE_ACCOUNT_KEY=$AZURE_ACCOUNT_KEY|" "${CLUSTER_CONFIG_DIR}/openvidu.env" +sed -i "s|AZURE_CONTAINER_NAME=.*|AZURE_CONTAINER_NAME=$AZURE_CONTAINER_NAME|" "${CLUSTER_CONFIG_DIR}/openvidu.env" ''' var installScriptMaster = reduce( @@ -935,7 +935,7 @@ var store_secretScriptMaster = reduce( ).value var blobStorageParams = { - storageAccountName: storageAccount.name + storageAccountName: isEmptyStorageAccountName ? storageAccount.name : exisitngStorageAccount.name storageAccountKey: listKeys(storageAccount.id, '2021-04-01').keys[0].value storageAccountContainerName: isEmptyContainerName ? 'openvidu-appdata' : '${containerName}' } @@ -967,7 +967,7 @@ var userDataParamsMasterNode = { base64restart: base64restartMaster base64config_blobStorage: base64config_blobStorage keyVaultName: keyVaultName - storageAccountName: storageAccount.name + storageAccountName: isEmptyStorageAccountName ? storageAccount.name : exisitngStorageAccount.name } var userDataTemplateMasterNode = ''' @@ -1168,7 +1168,7 @@ var stopMediaNodeParams = { subscriptionId: subscription().subscriptionId resourceGroupName: resourceGroup().name vmScaleSetName: '${stackName}-mediaNodeScaleSet' - storageAccountName: storageAccount.name + storageAccountName: isEmptyStorageAccountName ? storageAccount.name : exisitngStorageAccount.name } var stop_media_nodesScriptMediaTemplate = ''' @@ -1286,7 +1286,7 @@ resource openviduScaleSetMediaNode 'Microsoft.Compute/virtualMachineScaleSets@20 location: location tags: { InstanceDeleteTime: datetime - storageAccount: storageAccount.name + storageAccount: isEmptyStorageAccountName ? storageAccount.name : exisitngStorageAccount.name } identity: { type: 'SystemAssigned' } sku: { @@ -2028,7 +2028,12 @@ resource masterToMediaHttpWhipIngress 'Microsoft.Network/networkSecurityGroups/s /*------------------------------------------- STORAGE ACCOUNT ----------------------------------------*/ -resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = { +@description('Name of the existing storage account. It is essential that this parameter is filled just when you want to save recordings and still using the same container after an update. If not specified, a new storage account will be generated.') +param storageAccountName string = '' + +var isEmptyStorageAccountName = storageAccountName == '' + +resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = if (isEmptyStorageAccountName == true) { name: uniqueString(resourceGroup().id) location: resourceGroup().location sku: { @@ -2041,7 +2046,11 @@ resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = { } } -resource blobContainerScaleIn 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = { +resource exisitngStorageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' existing = if (isEmptyStorageAccountName == false) { + name: storageAccountName +} + +resource blobContainerScaleIn 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = if (isEmptyStorageAccountName == true) { name: '${storageAccount.name}/default/automation-locks' properties: { publicAccess: 'None' @@ -2053,7 +2062,7 @@ param containerName string = '' var isEmptyContainerName = containerName == '' -resource blobContainer 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = { +resource blobContainer 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = if (isEmptyStorageAccountName == true) { name: isEmptyContainerName ? '${storageAccount.name}/default/openvidu-appdata' : '${storageAccount.name}/default/${containerName}' diff --git a/openvidu-deployment/pro/elastic/azure/cf-openvidu-elastic.json b/openvidu-deployment/pro/elastic/azure/cf-openvidu-elastic.json index b2b2e5d6..7a9050cf 100644 --- a/openvidu-deployment/pro/elastic/azure/cf-openvidu-elastic.json +++ b/openvidu-deployment/pro/elastic/azure/cf-openvidu-elastic.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.34.44.8038", - "templateHash": "7933141956274941248" + "version": "0.35.1.17967", + "templateHash": "8963327612408018120" } }, "parameters": { @@ -389,6 +389,13 @@ "description": "Automation Account Name to create a runbook inside it for scale in" } }, + "storageAccountName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing storage account. It is essential that this parameter is filled just when you want to save recordings and still using the same container after an update. If not specified, a new storage account will be generated." + } + }, "containerName": { "type": "string", "defaultValue": "", @@ -477,7 +484,7 @@ "store_secretScriptTemplateMaster": "#!/bin/bash\nset -e\n\naz login --identity --allow-no-subscriptions > /dev/null\n\n# Modes: save, generate\n# save mode: save the secret in the secret manager\n# generate mode: generate a random password and save it in the secret manager\nMODE=\"$1\"\n\nif [[ \"$MODE\" == \"generate\" ]]; then\n SECRET_KEY_NAME=\"$2\"\n PREFIX=\"${3:-}\"\n LENGTH=\"${4:-44}\"\n RANDOM_PASSWORD=\"$(openssl rand -base64 64 | tr -d '+/=\\n' | cut -c -${LENGTH})\"\n RANDOM_PASSWORD=\"${PREFIX}${RANDOM_PASSWORD}\"\n az keyvault secret set --vault-name ${keyVaultName} --name $SECRET_KEY_NAME --value $RANDOM_PASSWORD > /dev/null\n if [[ $? -ne 0 ]]; then\n echo \"Error generating secret\"\n fi\n echo \"$RANDOM_PASSWORD\"\nelif [[ \"$MODE\" == \"save\" ]]; then\n SECRET_KEY_NAME=\"$2\"\n SECRET_VALUE=\"$3\"\n az keyvault secret set --vault-name ${keyVaultName} --name $SECRET_KEY_NAME --value $SECRET_VALUE > /dev/null\n if [[ $? -ne 0 ]]; then\n echo \"Error generating secret\"\n fi\n echo \"$SECRET_VALUE\"\nelse\n exit 1\nfi\n", "check_app_readyScriptMaster": "#!/bin/bash\nset -e\nwhile true; do\n HTTP_STATUS=$(curl -Ik http://localhost:7880/twirp/health | head -n1 | awk '{print $2}')\n if [ $HTTP_STATUS == 200 ]; then\n break\n fi\n sleep 5\ndone\n", "restartScriptMaster": "#!/bin/bash\nset -e\n# Stop all services\nsystemctl stop openvidu\n\n# Update config from secret\n/usr/local/bin/update_config_from_secret.sh\n\n# Start all services\nsystemctl start openvidu\n", - "config_blobStorageTemplate": "#!/bin/bash\nset -e\n\n# Install dir and config dir\nINSTALL_DIR=\"/opt/openvidu\"\nCONFIG_DIR=\"${INSTALL_DIR}/config\"\n\naz login --identity\n\n# Config azure blob storage\nAZURE_ACCOUNT_NAME=\"${storageAccountName}\"\nAZURE_ACCOUNT_KEY=$(az storage account keys list --account-name ${storageAccountName} --query '[0].value' -o tsv)\nAZURE_CONTAINER_NAME=\"${storageAccountContainerName}\"\n\nsed -i \"s|AZURE_ACCOUNT_NAME=.*|AZURE_ACCOUNT_NAME=$AZURE_ACCOUNT_NAME|\" \"${CONFIG_DIR}/openvidu.env\"\nsed -i \"s|AZURE_ACCOUNT_KEY=.*|AZURE_ACCOUNT_KEY=$AZURE_ACCOUNT_KEY|\" \"${CONFIG_DIR}/openvidu.env\"\nsed -i \"s|AZURE_CONTAINER_NAME=.*|AZURE_CONTAINER_NAME=$AZURE_CONTAINER_NAME|\" \"${CONFIG_DIR}/openvidu.env\"\n", + "config_blobStorageTemplate": "#!/bin/bash\nset -e\n\n# Install dir and config dir\nINSTALL_DIR=\"/opt/openvidu\"\nCLUSTER_CONFIG_DIR=\"${INSTALL_DIR}/config/cluster\"\n\naz login --identity\n\n# Config azure blob storage\nAZURE_ACCOUNT_NAME=\"${storageAccountName}\"\nAZURE_ACCOUNT_KEY=$(az storage account keys list --account-name ${storageAccountName} --query '[0].value' -o tsv)\nAZURE_CONTAINER_NAME=\"${storageAccountContainerName}\"\n\nsed -i \"s|AZURE_ACCOUNT_NAME=.*|AZURE_ACCOUNT_NAME=$AZURE_ACCOUNT_NAME|\" \"${CLUSTER_CONFIG_DIR}/openvidu.env\"\nsed -i \"s|AZURE_ACCOUNT_KEY=.*|AZURE_ACCOUNT_KEY=$AZURE_ACCOUNT_KEY|\" \"${CLUSTER_CONFIG_DIR}/openvidu.env\"\nsed -i \"s|AZURE_CONTAINER_NAME=.*|AZURE_CONTAINER_NAME=$AZURE_CONTAINER_NAME|\" \"${CLUSTER_CONFIG_DIR}/openvidu.env\"\n", "base64get_value_from_configMaster": "[base64(variables('get_value_from_configScriptMaster'))]", "base64check_app_readyMaster": "[base64(variables('check_app_readyScriptMaster'))]", "base64restartMaster": "[base64(variables('restartScriptMaster'))]", @@ -487,12 +494,13 @@ "subscriptionId": "[subscription().subscriptionId]", "resourceGroupName": "[resourceGroup().name]", "vmScaleSetName": "[format('{0}-mediaNodeScaleSet', parameters('stackName'))]", - "storageAccountName": "[uniqueString(resourceGroup().id)]" + "storageAccountName": "[if(variables('isEmptyStorageAccountName'), uniqueString(resourceGroup().id), uniqueString(resourceGroup().id))]" }, "stop_media_nodesScriptMediaTemplate": "#!/bin/bash\nset -e\n\nif ! (set -o noclobber ; echo > /tmp/global.lock) ; then\n exit 1 # the global.lock already exists\nfi\n\n# Execute if docker is installed\nif [ -x \"$(command -v docker)\" ]; then\n\n echo \"Stopping media node services and waiting for termination...\"\n docker container kill --signal=SIGINT openvidu || true\n docker container kill --signal=SIGINT ingress || true\n docker container kill --signal=SIGINT egress || true\n\n # Wait for running containers to not be openvidu, ingress or egress\n while [ $(docker inspect -f '{{.State.Running}}' openvidu 2>/dev/null) == \"true\" ] || \\\n [ $(docker inspect -f '{{.State.Running}}' ingress 2>/dev/null) == \"true\" ] || \\\n [ $(docker inspect -f '{{.State.Running}}' egress 2>/dev/null) == \"true\" ]; do\n echo \"Waiting for containers to stop...\"\n sleep 5\n done\nfi\n\naz login --identity\n\nRESOURCE_GROUP_NAME=${resourceGroupName}\nVM_SCALE_SET_NAME=${vmScaleSetName}\nSUBSCRIPTION_ID=${subscriptionId}\nBEFORE_INSTANCE_ID=$(curl -H Metadata:true --noproxy \"*\" \"http://169.254.169.254/metadata/instance?api-version=2021-02-01\" | jq -r '.compute.resourceId')\nINSTANCE_ID=$(echo $BEFORE_INSTANCE_ID | awk -F'/' '{print $NF}')\nRESOURCE_ID=/subscriptions/$SUBSCRIPTION_ID/resourcegroups/$RESOURCE_GROUP_NAME/providers/Microsoft.Compute/virtualMachineScaleSets/$VM_SCALE_SET_NAME\nTIMESTAMP=$(date -u +\"%Y-%m-%dT%H:%M:%SZ\")\n\naz tag update --resource-id $RESOURCE_ID --operation replace --tags \"STATUS\"=\"HEALTHY\" \"InstanceDeleteTime\"=\"$TIMESTAMP\" \"storageAccount\"=\"${storageAccountName}\"\n\naz vmss delete-instances --resource-group $RESOURCE_GROUP_NAME --name $VM_SCALE_SET_NAME --instance-ids $INSTANCE_ID\n", "userDataMediaNodeTemplate": "#!/bin/bash -x\nset -eu -o pipefail\n\n# Introduce the scripts in the instance\n# install.sh\necho ${base64install} | base64 -d > /usr/local/bin/install.sh\nchmod +x /usr/local/bin/install.sh\n\n# stop_media_nodes.sh\necho ${base64stop} | base64 -d > /usr/local/bin/stop_media_node.sh\nchmod +x /usr/local/bin/stop_media_node.sh\n\napt-get update && apt-get install -y\napt-get install -y jq\n\n# Install azure cli\ncurl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash\n\naz login --identity\n\n# Protect from scale in actions\nRESOURCE_GROUP_NAME=${resourceGroupName}\nVM_SCALE_SET_NAME=${vmScaleSetName}\nBEFORE_INSTANCE_ID=$(curl -H Metadata:true --noproxy \"*\" \"http://169.254.169.254/metadata/instance?api-version=2021-02-01\" | jq -r '.compute.resourceId')\nINSTANCE_ID=$(echo $BEFORE_INSTANCE_ID | awk -F'/' '{print $NF}')\naz vmss update --resource-group $RESOURCE_GROUP_NAME --name $VM_SCALE_SET_NAME --instance-id $INSTANCE_ID --protect-from-scale-in true\n\nexport HOME=\"/root\"\n\n# Install OpenVidu\n/usr/local/bin/install.sh || { echo \"[OpenVidu] error installing OpenVidu\"; exit 1; }\n\n# Start OpenVidu\nsystemctl start openvidu || { echo \"[OpenVidu] error starting OpenVidu\"; exit 1; }\n#/usr/local/bin/set_as_unhealthy.sh\n", "stop_media_nodesScriptMedia": "[reduce(items(variables('stopMediaNodeParams')), createObject('value', variables('stop_media_nodesScriptMediaTemplate')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value]", "base64stopMediaNode": "[base64(variables('stop_media_nodesScriptMedia'))]", + "isEmptyStorageAccountName": "[equals(parameters('storageAccountName'), '')]", "isEmptyContainerName": "[equals(parameters('containerName'), '')]" }, "resources": [ @@ -593,7 +601,7 @@ "adminPassword": "[parameters('adminSshKey')]", "linuxConfiguration": "[variables('masterNodeVMSettings').linuxConfiguration]" }, - "userData": "[base64(reduce(items(createObject('base64install', base64(reduce(items(createObject('domainName', parameters('domainName'), 'fqdn', if(variables('isEmptyIp'), reference(resourceId('Microsoft.Network/publicIPAddresses', format('{0}-publicIP', parameters('stackName'))), '2023-11-01').dnsSettings.fqdn, parameters('domainName')), 'turnDomainName', parameters('turnDomainName'), 'certificateType', parameters('certificateType'), 'letsEncryptEmail', parameters('letsEncryptEmail'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'turnOwnPublicCertificate', parameters('turnOwnPublicCertificate'), 'turnOwnPrivateCertificate', parameters('turnOwnPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'keyVaultName', variables('keyVaultName'))), createObject('value', variables('installScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64after_install', base64(reduce(items(createObject('domainName', parameters('domainName'), 'fqdn', if(variables('isEmptyIp'), reference(resourceId('Microsoft.Network/publicIPAddresses', format('{0}-publicIP', parameters('stackName'))), '2023-11-01').dnsSettings.fqdn, parameters('domainName')), 'turnDomainName', parameters('turnDomainName'), 'certificateType', parameters('certificateType'), 'letsEncryptEmail', parameters('letsEncryptEmail'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'turnOwnPublicCertificate', parameters('turnOwnPublicCertificate'), 'turnOwnPrivateCertificate', parameters('turnOwnPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'keyVaultName', variables('keyVaultName'))), createObject('value', variables('after_installScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64update_config_from_secret', base64(reduce(items(createObject('domainName', parameters('domainName'), 'fqdn', if(variables('isEmptyIp'), reference(resourceId('Microsoft.Network/publicIPAddresses', format('{0}-publicIP', parameters('stackName'))), '2023-11-01').dnsSettings.fqdn, parameters('domainName')), 'turnDomainName', parameters('turnDomainName'), 'certificateType', parameters('certificateType'), 'letsEncryptEmail', parameters('letsEncryptEmail'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'turnOwnPublicCertificate', parameters('turnOwnPublicCertificate'), 'turnOwnPrivateCertificate', parameters('turnOwnPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'keyVaultName', variables('keyVaultName'))), createObject('value', variables('update_config_from_secretScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64update_secret_from_config', base64(reduce(items(createObject('domainName', parameters('domainName'), 'fqdn', if(variables('isEmptyIp'), reference(resourceId('Microsoft.Network/publicIPAddresses', format('{0}-publicIP', parameters('stackName'))), '2023-11-01').dnsSettings.fqdn, parameters('domainName')), 'turnDomainName', parameters('turnDomainName'), 'certificateType', parameters('certificateType'), 'letsEncryptEmail', parameters('letsEncryptEmail'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'turnOwnPublicCertificate', parameters('turnOwnPublicCertificate'), 'turnOwnPrivateCertificate', parameters('turnOwnPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'keyVaultName', variables('keyVaultName'))), createObject('value', variables('update_secret_from_configScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64get_value_from_config', variables('base64get_value_from_configMaster'), 'base64store_secret', base64(reduce(items(createObject('domainName', parameters('domainName'), 'fqdn', if(variables('isEmptyIp'), reference(resourceId('Microsoft.Network/publicIPAddresses', format('{0}-publicIP', parameters('stackName'))), '2023-11-01').dnsSettings.fqdn, parameters('domainName')), 'turnDomainName', parameters('turnDomainName'), 'certificateType', parameters('certificateType'), 'letsEncryptEmail', parameters('letsEncryptEmail'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'turnOwnPublicCertificate', parameters('turnOwnPublicCertificate'), 'turnOwnPrivateCertificate', parameters('turnOwnPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'keyVaultName', variables('keyVaultName'))), createObject('value', variables('store_secretScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64check_app_ready', variables('base64check_app_readyMaster'), 'base64restart', variables('base64restartMaster'), 'base64config_blobStorage', base64(reduce(items(createObject('storageAccountName', uniqueString(resourceGroup().id), 'storageAccountKey', listKeys(resourceId('Microsoft.Storage/storageAccounts', uniqueString(resourceGroup().id)), '2021-04-01').keys[0].value, 'storageAccountContainerName', if(variables('isEmptyContainerName'), 'openvidu-appdata', format('{0}', parameters('containerName'))))), createObject('value', variables('config_blobStorageTemplate')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'keyVaultName', variables('keyVaultName'), 'storageAccountName', uniqueString(resourceGroup().id))), createObject('value', variables('userDataTemplateMasterNode')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value)]" + "userData": "[base64(reduce(items(createObject('base64install', base64(reduce(items(createObject('domainName', parameters('domainName'), 'fqdn', if(variables('isEmptyIp'), reference(resourceId('Microsoft.Network/publicIPAddresses', format('{0}-publicIP', parameters('stackName'))), '2023-11-01').dnsSettings.fqdn, parameters('domainName')), 'turnDomainName', parameters('turnDomainName'), 'certificateType', parameters('certificateType'), 'letsEncryptEmail', parameters('letsEncryptEmail'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'turnOwnPublicCertificate', parameters('turnOwnPublicCertificate'), 'turnOwnPrivateCertificate', parameters('turnOwnPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'keyVaultName', variables('keyVaultName'))), createObject('value', variables('installScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64after_install', base64(reduce(items(createObject('domainName', parameters('domainName'), 'fqdn', if(variables('isEmptyIp'), reference(resourceId('Microsoft.Network/publicIPAddresses', format('{0}-publicIP', parameters('stackName'))), '2023-11-01').dnsSettings.fqdn, parameters('domainName')), 'turnDomainName', parameters('turnDomainName'), 'certificateType', parameters('certificateType'), 'letsEncryptEmail', parameters('letsEncryptEmail'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'turnOwnPublicCertificate', parameters('turnOwnPublicCertificate'), 'turnOwnPrivateCertificate', parameters('turnOwnPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'keyVaultName', variables('keyVaultName'))), createObject('value', variables('after_installScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64update_config_from_secret', base64(reduce(items(createObject('domainName', parameters('domainName'), 'fqdn', if(variables('isEmptyIp'), reference(resourceId('Microsoft.Network/publicIPAddresses', format('{0}-publicIP', parameters('stackName'))), '2023-11-01').dnsSettings.fqdn, parameters('domainName')), 'turnDomainName', parameters('turnDomainName'), 'certificateType', parameters('certificateType'), 'letsEncryptEmail', parameters('letsEncryptEmail'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'turnOwnPublicCertificate', parameters('turnOwnPublicCertificate'), 'turnOwnPrivateCertificate', parameters('turnOwnPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'keyVaultName', variables('keyVaultName'))), createObject('value', variables('update_config_from_secretScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64update_secret_from_config', base64(reduce(items(createObject('domainName', parameters('domainName'), 'fqdn', if(variables('isEmptyIp'), reference(resourceId('Microsoft.Network/publicIPAddresses', format('{0}-publicIP', parameters('stackName'))), '2023-11-01').dnsSettings.fqdn, parameters('domainName')), 'turnDomainName', parameters('turnDomainName'), 'certificateType', parameters('certificateType'), 'letsEncryptEmail', parameters('letsEncryptEmail'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'turnOwnPublicCertificate', parameters('turnOwnPublicCertificate'), 'turnOwnPrivateCertificate', parameters('turnOwnPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'keyVaultName', variables('keyVaultName'))), createObject('value', variables('update_secret_from_configScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64get_value_from_config', variables('base64get_value_from_configMaster'), 'base64store_secret', base64(reduce(items(createObject('domainName', parameters('domainName'), 'fqdn', if(variables('isEmptyIp'), reference(resourceId('Microsoft.Network/publicIPAddresses', format('{0}-publicIP', parameters('stackName'))), '2023-11-01').dnsSettings.fqdn, parameters('domainName')), 'turnDomainName', parameters('turnDomainName'), 'certificateType', parameters('certificateType'), 'letsEncryptEmail', parameters('letsEncryptEmail'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'turnOwnPublicCertificate', parameters('turnOwnPublicCertificate'), 'turnOwnPrivateCertificate', parameters('turnOwnPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'keyVaultName', variables('keyVaultName'))), createObject('value', variables('store_secretScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64check_app_ready', variables('base64check_app_readyMaster'), 'base64restart', variables('base64restartMaster'), 'base64config_blobStorage', base64(reduce(items(createObject('storageAccountName', if(variables('isEmptyStorageAccountName'), uniqueString(resourceGroup().id), uniqueString(resourceGroup().id)), 'storageAccountKey', listKeys(resourceId('Microsoft.Storage/storageAccounts', uniqueString(resourceGroup().id)), '2021-04-01').keys[0].value, 'storageAccountContainerName', if(variables('isEmptyContainerName'), 'openvidu-appdata', format('{0}', parameters('containerName'))))), createObject('value', variables('config_blobStorageTemplate')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'keyVaultName', variables('keyVaultName'), 'storageAccountName', if(variables('isEmptyStorageAccountName'), uniqueString(resourceGroup().id), uniqueString(resourceGroup().id)))), createObject('value', variables('userDataTemplateMasterNode')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value)]" }, "dependsOn": [ "[resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNoderNetInterface', parameters('stackName')))]", @@ -608,7 +616,7 @@ "location": "[variables('location')]", "tags": { "InstanceDeleteTime": "[parameters('datetime')]", - "storageAccount": "[uniqueString(resourceGroup().id)]" + "storageAccount": "[if(variables('isEmptyStorageAccountName'), uniqueString(resourceGroup().id), uniqueString(resourceGroup().id))]" }, "identity": { "type": "SystemAssigned" @@ -1423,6 +1431,7 @@ ] }, { + "condition": "[equals(variables('isEmptyStorageAccountName'), true())]", "type": "Microsoft.Storage/storageAccounts", "apiVersion": "2023-01-01", "name": "[uniqueString(resourceGroup().id)]", @@ -1437,6 +1446,7 @@ } }, { + "condition": "[equals(variables('isEmptyStorageAccountName'), true())]", "type": "Microsoft.Storage/storageAccounts/blobServices/containers", "apiVersion": "2023-01-01", "name": "[format('{0}/default/automation-locks', uniqueString(resourceGroup().id))]", @@ -1448,6 +1458,7 @@ ] }, { + "condition": "[equals(variables('isEmptyStorageAccountName'), true())]", "type": "Microsoft.Storage/storageAccounts/blobServices/containers", "apiVersion": "2023-01-01", "name": "[if(variables('isEmptyContainerName'), format('{0}/default/openvidu-appdata', uniqueString(resourceGroup().id)), format('{0}/default/{1}', uniqueString(resourceGroup().id), parameters('containerName')))]", diff --git a/openvidu-deployment/pro/ha/azure/cf-openvidu-ha.bicep b/openvidu-deployment/pro/ha/azure/cf-openvidu-ha.bicep index af138008..213332aa 100644 --- a/openvidu-deployment/pro/ha/azure/cf-openvidu-ha.bicep +++ b/openvidu-deployment/pro/ha/azure/cf-openvidu-ha.bicep @@ -15,7 +15,7 @@ and an Elastic IP, you can use this option to generate a Let's Encrypt certifica ]) param certificateType string = 'selfsigned' -@description('Domain name for the OpenVidu Deployment. Blank will generate default domain') +@description('Domain name for the OpenVidu Deployment.') param domainName string @description('If certificate type is \'owncert\', this parameter will be used to specify the public certificate') @@ -986,6 +986,26 @@ systemctl stop openvidu systemctl start openvidu ''' +var config_blobStorageTemplate = ''' +#!/bin/bash +set -e + +# Install dir and config dir +INSTALL_DIR="/opt/openvidu" +CLUSTER_CONFIG_DIR="${INSTALL_DIR}/config/cluster" + +az login --identity + +# Config azure blob storage +AZURE_ACCOUNT_NAME="${storageAccountName}" +AZURE_ACCOUNT_KEY=$(az storage account keys list --account-name ${storageAccountName} --query '[0].value' -o tsv) +AZURE_CONTAINER_NAME="${storageAccountContainerName}" + +sed -i "s|AZURE_ACCOUNT_NAME=.*|AZURE_ACCOUNT_NAME=$AZURE_ACCOUNT_NAME|" "${CLUSTER_CONFIG_DIR}/openvidu.env" +sed -i "s|AZURE_ACCOUNT_KEY=.*|AZURE_ACCOUNT_KEY=$AZURE_ACCOUNT_KEY|" "${CLUSTER_CONFIG_DIR}/openvidu.env" +sed -i "s|AZURE_CONTAINER_NAME=.*|AZURE_CONTAINER_NAME=$AZURE_CONTAINER_NAME|" "${CLUSTER_CONFIG_DIR}/openvidu.env" +''' + var installScriptMaster1 = reduce( items(stringInterpolationParamsMaster1), { value: installScriptTemplateMaster }, @@ -1034,6 +1054,18 @@ var store_secretScriptMaster = reduce( (curr, next) => { value: replace(curr.value, '\${${next.key}}', next.value) } ).value +var blobStorageParams = { + storageAccountName: isEmptyStorageAccountName ? storageAccount.name : exisitngStorageAccount.name + storageAccountKey: listKeys(storageAccount.id, '2021-04-01').keys[0].value + storageAccountContainerName: isEmptyContainerName ? 'openvidu-appdata' : '${containerName}' +} + +var config_blobStorageScript = reduce( + items(blobStorageParams), + { value: config_blobStorageTemplate }, + (curr, next) => { value: replace(curr.value, '\${${next.key}}', next.value) } +).value + var base64installMaster1 = base64(installScriptMaster1) var base64installMaster2 = base64(installScriptMaster2) var base64installMaster3 = base64(installScriptMaster3) @@ -1045,6 +1077,7 @@ var base64get_value_from_configMaster = base64(get_value_from_configScriptMaster var base64store_secretMaster = base64(store_secretScriptMaster) var base64check_app_readyMaster = base64(check_app_readyScriptMaster) var base64restartMaster = base64(restartScriptMaster) +var base64config_blobStorage = base64(config_blobStorageScript) var userDataParamsMasterNode1 = { base64install: base64installMaster1 @@ -1096,7 +1129,8 @@ var userDataParamsMasterNode4 = { base64restart: base64restartMaster keyVaultName: keyVaultName masterNodeNum: '4' - storageAccountName: storageAccount.name + storageAccountName: isEmptyStorageAccountName ? storageAccount.name : exisitngStorageAccount.name + base64config_blobStorage: base64config_blobStorage } var userDataTemplateMasterNode = ''' @@ -1159,9 +1193,17 @@ echo "@reboot /usr/local/bin/restart.sh >> /var/log/openvidu-restart.log" 2>&1 | MASTER_NODE_NUM=${masterNodeNum} if [[ $MASTER_NODE_NUM -eq 4 ]]; then + # Creating scale in lock set +e az storage blob upload --account-name ${storageAccountName} --container-name automation-locks --name lock.txt --file /dev/null --auth-mode key set -e + + # Configuring blob storage + echo ${base64config_blobStorage} | base64 -d > /usr/local/bin/config_blobStorage.sh + chmod +x /usr/local/bin/config_blobStorage.sh + /usr/local/bin/config_blobStorage.sh || { echo "[OpenVidu] error configuring Blob Storage"; exit 1; } + + #Finish all the nodes az keyvault secret set --vault-name ${keyVaultName} --name FINISH-MASTER-NODE --value "true" fi @@ -1426,7 +1468,7 @@ var stopMediaNodeParams = { subscriptionId: subscription().subscriptionId resourceGroupName: resourceGroup().name vmScaleSetName: '${stackName}-mediaNodeScaleSet' - storageAccountName: storageAccount.name + storageAccountName: isEmptyStorageAccountName ? storageAccount.name : exisitngStorageAccount.name } var stop_media_nodesScriptMediaTemplate = ''' @@ -1524,6 +1566,8 @@ var base64stopMediaNode = base64(stop_media_nodesScriptMedia) var userDataParamsMedia = { base64install: base64installMedia base64stop: base64stopMediaNode + resourceGroupName: resourceGroup().name + vmScaleSetName: '${stackName}-mediaNodeScaleSet' } var userDataMediaNode = reduce( @@ -1540,7 +1584,7 @@ resource openviduScaleSetMediaNode 'Microsoft.Compute/virtualMachineScaleSets@20 location: location tags: { InstanceDeleteTime: datetime - storageAccount: storageAccount.name + storageAccount: isEmptyStorageAccountName ? storageAccount.name : exisitngStorageAccount.name } identity: { type: 'SystemAssigned' } sku: { @@ -2922,7 +2966,12 @@ resource masterToMediaClientIngress 'Microsoft.Network/networkSecurityGroups/sec /*------------------------------------------- STORAGE ACCOUNT ----------------------------------------*/ -resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = { +@description('Name of an existing storage account. It is essential that this parameter is filled just when you want to save recordings and still using the same container after an update. If not specified, a new storage account will be generated.') +param storageAccountName string = '' + +var isEmptyStorageAccountName = storageAccountName == '' + +resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = if (isEmptyStorageAccountName == true) { name: uniqueString(resourceGroup().id) location: resourceGroup().location sku: { @@ -2935,19 +2984,23 @@ resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = { } } -resource blobContainerScaleIn 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = { +resource exisitngStorageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' existing = if (isEmptyStorageAccountName == false) { + name: storageAccountName +} + +resource blobContainerScaleIn 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = if (isEmptyStorageAccountName == true) { name: '${storageAccount.name}/default/automation-locks' properties: { publicAccess: 'None' } } -@description('Name of the bucket where OpenVidu will store the recordings. If not specified, a default bucket will be created.') +@description('Name of the bucket where OpenVidu will store the recordings if a new Storage account is being creating. If not specified, a default bucket will be created.') param containerName string = '' var isEmptyContainerName = containerName == '' -resource blobContainer 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = { +resource blobContainer 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = if (isEmptyStorageAccountName == true) { name: isEmptyContainerName ? '${storageAccount.name}/default/openvidu-appdata' : '${storageAccount.name}/default/${containerName}' diff --git a/openvidu-deployment/pro/ha/azure/cf-openvidu-ha.json b/openvidu-deployment/pro/ha/azure/cf-openvidu-ha.json index 9d93d610..4d682051 100644 --- a/openvidu-deployment/pro/ha/azure/cf-openvidu-ha.json +++ b/openvidu-deployment/pro/ha/azure/cf-openvidu-ha.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.34.44.8038", - "templateHash": "5609072357229822186" + "version": "0.35.1.17967", + "templateHash": "4378002790668359491" } }, "parameters": { @@ -30,7 +30,7 @@ "domainName": { "type": "string", "metadata": { - "description": "Domain name for the OpenVidu Deployment. Blank will generate default domain" + "description": "Domain name for the OpenVidu Deployment." } }, "ownPublicCertificate": { @@ -395,11 +395,18 @@ "description": "Automation Account Name to create a runbook inside it for scale in" } }, + "storageAccountName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of an existing storage account. It is essential that this parameter is filled just when you want to save recordings and still using the same container after an update. If not specified, a new storage account will be generated." + } + }, "containerName": { "type": "string", "defaultValue": "", "metadata": { - "description": "Name of the bucket where OpenVidu will store the recordings. If not specified, a default bucket will be created." + "description": "Name of the bucket where OpenVidu will store the recordings if a new Storage account is being creating. If not specified, a default bucket will be created." } } }, @@ -520,6 +527,7 @@ "store_secretScriptTemplateMaster": "#!/bin/bash\nset -e\n\naz login --identity --allow-no-subscriptions > /dev/null\n\n# Modes: save, generate\n# save mode: save the secret in the secret manager\n# generate mode: generate a random password and save it in the secret manager\nMODE=\"$1\"\n\nif [[ \"$MODE\" == \"generate\" ]]; then\n SECRET_KEY_NAME=\"$2\"\n PREFIX=\"${3:-}\"\n LENGTH=\"${4:-44}\"\n RANDOM_PASSWORD=\"$(openssl rand -base64 64 | tr -d '+/=\\n' | cut -c -${LENGTH})\"\n RANDOM_PASSWORD=\"${PREFIX}${RANDOM_PASSWORD}\"\n az keyvault secret set --vault-name ${keyVaultName} --name $SECRET_KEY_NAME --value $RANDOM_PASSWORD > /dev/null\n if [[ $? -ne 0 ]]; then\n echo \"Error generating secret\"\n fi\n echo \"$RANDOM_PASSWORD\"\nelif [[ \"$MODE\" == \"save\" ]]; then\n SECRET_KEY_NAME=\"$2\"\n SECRET_VALUE=\"$3\"\n az keyvault secret set --vault-name ${keyVaultName} --name $SECRET_KEY_NAME --value $SECRET_VALUE > /dev/null\n if [[ $? -ne 0 ]]; then\n echo \"Error generating secret\"\n fi\n echo \"$SECRET_VALUE\"\nelse\n exit 1\nfi\n", "check_app_readyScriptMaster": "#!/bin/bash\nset -e\nwhile true; do\n HTTP_STATUS=$(curl -Ik http://localhost:7880/twirp/health | head -n1 | awk '{print $2}')\n if [ $HTTP_STATUS == 200 ]; then\n break\n fi\n sleep 5\ndone\n", "restartScriptMaster": "#!/bin/bash\nset -e\n# Stop all services\nsystemctl stop openvidu\n\n# Update config from secret\n/usr/local/bin/update_config_from_secret.sh\n\n# Start all services\nsystemctl start openvidu\n", + "config_blobStorageTemplate": "#!/bin/bash\nset -e\n\n# Install dir and config dir\nINSTALL_DIR=\"/opt/openvidu\"\nCLUSTER_CONFIG_DIR=\"${INSTALL_DIR}/config/cluster\"\n\naz login --identity\n\n# Config azure blob storage\nAZURE_ACCOUNT_NAME=\"${storageAccountName}\"\nAZURE_ACCOUNT_KEY=$(az storage account keys list --account-name ${storageAccountName} --query '[0].value' -o tsv)\nAZURE_CONTAINER_NAME=\"${storageAccountContainerName}\"\n\nsed -i \"s|AZURE_ACCOUNT_NAME=.*|AZURE_ACCOUNT_NAME=$AZURE_ACCOUNT_NAME|\" \"${CLUSTER_CONFIG_DIR}/openvidu.env\"\nsed -i \"s|AZURE_ACCOUNT_KEY=.*|AZURE_ACCOUNT_KEY=$AZURE_ACCOUNT_KEY|\" \"${CLUSTER_CONFIG_DIR}/openvidu.env\"\nsed -i \"s|AZURE_CONTAINER_NAME=.*|AZURE_CONTAINER_NAME=$AZURE_CONTAINER_NAME|\" \"${CLUSTER_CONFIG_DIR}/openvidu.env\"\n", "installScriptMaster1": "[reduce(items(variables('stringInterpolationParamsMaster1')), createObject('value', variables('installScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value]", "installScriptMaster2": "[reduce(items(variables('stringInterpolationParamsMaster2')), createObject('value', variables('installScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value]", "installScriptMaster3": "[reduce(items(variables('stringInterpolationParamsMaster3')), createObject('value', variables('installScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value]", @@ -575,30 +583,16 @@ "keyVaultName": "[variables('keyVaultName')]", "masterNodeNum": "3" }, - "userDataParamsMasterNode4": { - "base64install": "[variables('base64installMaster4')]", - "base64after_install": "[variables('base64after_installMaster')]", - "base64update_config_from_secret": "[variables('base64update_config_from_secretMaster')]", - "base64update_secret_from_config": "[variables('base64update_secret_from_configMaster')]", - "base64get_value_from_config": "[variables('base64get_value_from_configMaster')]", - "base64store_secret": "[variables('base64store_secretMaster')]", - "base64check_app_ready": "[variables('base64check_app_readyMaster')]", - "base64restart": "[variables('base64restartMaster')]", - "keyVaultName": "[variables('keyVaultName')]", - "masterNodeNum": "4", - "storageAccountName": "[uniqueString(resourceGroup().id)]" - }, - "userDataTemplateMasterNode": "#!/bin/bash -x\nset -eu -o pipefail\n\n# Introduce the scripts in the instance\n# install.sh\necho ${base64install} | base64 -d > /usr/local/bin/install.sh\nchmod +x /usr/local/bin/install.sh\n\n# after_install.sh\necho ${base64after_install} | base64 -d > /usr/local/bin/after_install.sh\nchmod +x /usr/local/bin/after_install.sh\n\n# update_config_from_secret.sh\necho ${base64update_config_from_secret} | base64 -d > /usr/local/bin/update_config_from_secret.sh\nchmod +x /usr/local/bin/update_config_from_secret.sh\n\n# update_secret_from_config.sh\necho ${base64update_secret_from_config} | base64 -d > /usr/local/bin/update_secret_from_config.sh\nchmod +x /usr/local/bin/update_secret_from_config.sh\n\n# get_value_from_config.sh\necho ${base64get_value_from_config} | base64 -d > /usr/local/bin/get_value_from_config.sh\nchmod +x /usr/local/bin/get_value_from_config.sh\n\n# store_secret.sh\necho ${base64store_secret} | base64 -d > /usr/local/bin/store_secret.sh\nchmod +x /usr/local/bin/store_secret.sh\n\n# check_app_ready.sh\necho ${base64check_app_ready} | base64 -d > /usr/local/bin/check_app_ready.sh\nchmod +x /usr/local/bin/check_app_ready.sh\n\n# restart.sh\necho ${base64restart} | base64 -d > /usr/local/bin/restart.sh\nchmod +x /usr/local/bin/restart.sh\n\n# Install azure cli\ncurl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash\n\naz login --identity --allow-no-subscriptions\n\napt-get update && apt-get install -y\n\nexport HOME=\"/root\"\n\n# Install OpenVidu\n/usr/local/bin/install.sh || { echo \"[OpenVidu] error installing OpenVidu\"; exit 1; }\n\n# Start OpenVidu\nsystemctl start openvidu || { echo \"[OpenVidu] error starting OpenVidu\"; exit 1; }\n\n# Update shared secret\n/usr/local/bin/after_install.sh || { echo \"[OpenVidu] error updating shared secret\"; exit 1; }\n\n# Launch on reboot\necho \"@reboot /usr/local/bin/restart.sh >> /var/log/openvidu-restart.log\" 2>&1 | crontab\n\nMASTER_NODE_NUM=${masterNodeNum}\nif [[ $MASTER_NODE_NUM -eq 4 ]]; then\n set +e\n az storage blob upload --account-name ${storageAccountName} --container-name automation-locks --name lock.txt --file /dev/null --auth-mode key\n set -e\n az keyvault secret set --vault-name ${keyVaultName} --name FINISH-MASTER-NODE --value \"true\"\nfi\n\n# Wait for the app\nsleep 150\n/usr/local/bin/check_app_ready.sh\n", + "userDataTemplateMasterNode": "#!/bin/bash -x\nset -eu -o pipefail\n\n# Introduce the scripts in the instance\n# install.sh\necho ${base64install} | base64 -d > /usr/local/bin/install.sh\nchmod +x /usr/local/bin/install.sh\n\n# after_install.sh\necho ${base64after_install} | base64 -d > /usr/local/bin/after_install.sh\nchmod +x /usr/local/bin/after_install.sh\n\n# update_config_from_secret.sh\necho ${base64update_config_from_secret} | base64 -d > /usr/local/bin/update_config_from_secret.sh\nchmod +x /usr/local/bin/update_config_from_secret.sh\n\n# update_secret_from_config.sh\necho ${base64update_secret_from_config} | base64 -d > /usr/local/bin/update_secret_from_config.sh\nchmod +x /usr/local/bin/update_secret_from_config.sh\n\n# get_value_from_config.sh\necho ${base64get_value_from_config} | base64 -d > /usr/local/bin/get_value_from_config.sh\nchmod +x /usr/local/bin/get_value_from_config.sh\n\n# store_secret.sh\necho ${base64store_secret} | base64 -d > /usr/local/bin/store_secret.sh\nchmod +x /usr/local/bin/store_secret.sh\n\n# check_app_ready.sh\necho ${base64check_app_ready} | base64 -d > /usr/local/bin/check_app_ready.sh\nchmod +x /usr/local/bin/check_app_ready.sh\n\n# restart.sh\necho ${base64restart} | base64 -d > /usr/local/bin/restart.sh\nchmod +x /usr/local/bin/restart.sh\n\n# Install azure cli\ncurl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash\n\naz login --identity --allow-no-subscriptions\n\napt-get update && apt-get install -y\n\nexport HOME=\"/root\"\n\n# Install OpenVidu\n/usr/local/bin/install.sh || { echo \"[OpenVidu] error installing OpenVidu\"; exit 1; }\n\n# Start OpenVidu\nsystemctl start openvidu || { echo \"[OpenVidu] error starting OpenVidu\"; exit 1; }\n\n# Update shared secret\n/usr/local/bin/after_install.sh || { echo \"[OpenVidu] error updating shared secret\"; exit 1; }\n\n# Launch on reboot\necho \"@reboot /usr/local/bin/restart.sh >> /var/log/openvidu-restart.log\" 2>&1 | crontab\n\nMASTER_NODE_NUM=${masterNodeNum}\nif [[ $MASTER_NODE_NUM -eq 4 ]]; then\n # Creating scale in lock\n set +e\n az storage blob upload --account-name ${storageAccountName} --container-name automation-locks --name lock.txt --file /dev/null --auth-mode key\n set -e\n \n # Configuring blob storage\n echo ${base64config_blobStorage} | base64 -d > /usr/local/bin/config_blobStorage.sh\n chmod +x /usr/local/bin/config_blobStorage.sh\n /usr/local/bin/config_blobStorage.sh || { echo \"[OpenVidu] error configuring Blob Storage\"; exit 1; }\n\n #Finish all the nodes\n az keyvault secret set --vault-name ${keyVaultName} --name FINISH-MASTER-NODE --value \"true\"\nfi\n\n# Wait for the app\nsleep 150\n/usr/local/bin/check_app_ready.sh\n", "userDataMasterNode1": "[reduce(items(variables('userDataParamsMasterNode1')), createObject('value', variables('userDataTemplateMasterNode')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value]", "userDataMasterNode2": "[reduce(items(variables('userDataParamsMasterNode2')), createObject('value', variables('userDataTemplateMasterNode')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value]", "userDataMasterNode3": "[reduce(items(variables('userDataParamsMasterNode3')), createObject('value', variables('userDataTemplateMasterNode')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value]", - "userDataMasterNode4": "[reduce(items(variables('userDataParamsMasterNode4')), createObject('value', variables('userDataTemplateMasterNode')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value]", "installScriptTemplateMedia": "#!/bin/bash -x\nDOMAIN=\n\n# Install dependencies\napt-get update && apt-get install -y \\\n curl \\\n unzip \\\n jq \\\n wget\n\n# Get own private IP\nPRIVATE_IP=$(curl -H Metadata:true --noproxy \"*\" \"http://169.254.169.254/metadata/instance/network/interface/0/ipv4/ipAddress/0/privateIpAddress?api-version=2017-08-01&format=text\")\n\nWAIT_INTERVAL=1\nMAX_WAIT=10000\nELAPSED_TIME=0\nset +e\nwhile true; do\n # get secret value\n FINISH_MASTER_NODE=$(az keyvault secret show --vault-name ${keyVaultName} --name FINISH-MASTER-NODE --query value -o tsv)\n\n # Check if all master nodes finished\n if [ \"$FINISH_MASTER_NODE\" == \"true\" ]; then\n break\n fi\n\n ELAPSED_TIME=$((ELAPSED_TIME + WAIT_INTERVAL))\n\n # Check if the maximum waiting time has been reached\n if [ $ELAPSED_TIME -ge $MAX_WAIT ]; then\n exit 1\n fi\n\n sleep $WAIT_INTERVAL\ndone\nset -e\n\nMASTER_NODE_1_PRIVATE_IP=$(az keyvault secret show --vault-name ${keyVaultName} --name MASTER-NODE-1-PRIVATE-IP --query value -o tsv)\nMASTER_NODE_2_PRIVATE_IP=$(az keyvault secret show --vault-name ${keyVaultName} --name MASTER-NODE-2-PRIVATE-IP --query value -o tsv)\nMASTER_NODE_3_PRIVATE_IP=$(az keyvault secret show --vault-name ${keyVaultName} --name MASTER-NODE-3-PRIVATE-IP --query value -o tsv)\nMASTER_NODE_4_PRIVATE_IP=$(az keyvault secret show --vault-name ${keyVaultName} --name MASTER-NODE-4-PRIVATE-IP --query value -o tsv)\nMASTER_NODE_PRIVATE_IP_LIST=\"$MASTER_NODE_1_PRIVATE_IP,$MASTER_NODE_2_PRIVATE_IP,$MASTER_NODE_3_PRIVATE_IP,$MASTER_NODE_4_PRIVATE_IP\"\nREDIS_PASSWORD=$(az keyvault secret show --vault-name ${keyVaultName} --name REDIS-PASSWORD --query value -o tsv)\nENABLED_MODULES=$(az keyvault secret show --vault-name ${keyVaultName} --name ENABLED-MODULES --query value -o tsv)\nOPENVIDU_VERSION=$(az keyvault secret show --vault-name ${keyVaultName} --name OPENVIDU-VERSION --query value -o tsv)\n\n# Base command\nINSTALL_COMMAND=\"sh <(curl -fsSL http://get.openvidu.io/pro/ha/$OPENVIDU_VERSION/install_ov_media_node.sh)\"\n\n# Common arguments\nCOMMON_ARGS=(\n\"--no-tty\"\n\"--install\"\n\"--environment=azure\"\n\"--deployment-type='ha'\"\n\"--node-role='media-node'\"\n\"--master-node-private-ip-list=$MASTER_NODE_PRIVATE_IP_LIST\"\n\"--private-ip=$PRIVATE_IP\"\n\"--enabled-modules='$ENABLED_MODULES'\"\n\"--redis-password=$REDIS_PASSWORD\"\n)\n\n# Construct the final command with all arguments\nFINAL_COMMAND=\"$INSTALL_COMMAND $(printf \"%s \" \"${COMMON_ARGS[@]}\")\"\n\n# Install OpenVidu\nexec bash -c \"$FINAL_COMMAND\"\n", "stopMediaNodeParams": { "subscriptionId": "[subscription().subscriptionId]", "resourceGroupName": "[resourceGroup().name]", "vmScaleSetName": "[format('{0}-mediaNodeScaleSet', parameters('stackName'))]", - "storageAccountName": "[uniqueString(resourceGroup().id)]" + "storageAccountName": "[if(variables('isEmptyStorageAccountName'), uniqueString(resourceGroup().id), uniqueString(resourceGroup().id))]" }, "stop_media_nodesScriptMediaTemplate": "#!/bin/bash\nset -e\n\nif ! (set -o noclobber ; echo > /tmp/global.lock) ; then\n exit 1 # the global.lock already exists\nfi\n\n# Execute if docker is installed\nif [ -x \"$(command -v docker)\" ]; then\n\n echo \"Stopping media node services and waiting for termination...\"\n docker container kill --signal=SIGINT openvidu || true\n docker container kill --signal=SIGINT ingress || true\n docker container kill --signal=SIGINT egress || true\n\n # Wait for running containers to not be openvidu, ingress or egress\n while [ $(docker inspect -f '{{.State.Running}}' openvidu 2>/dev/null) == \"true\" ] || \\\n [ $(docker inspect -f '{{.State.Running}}' ingress 2>/dev/null) == \"true\" ] || \\\n [ $(docker inspect -f '{{.State.Running}}' egress 2>/dev/null) == \"true\" ]; do\n echo \"Waiting for containers to stop...\"\n sleep 5\n done\nfi\n\naz login --identity\n\nRESOURCE_GROUP_NAME=${resourceGroupName}\nVM_SCALE_SET_NAME=${vmScaleSetName}\nSUBSCRIPTION_ID=${subscriptionId}\nBEFORE_INSTANCE_ID=$(curl -H Metadata:true --noproxy \"*\" \"http://169.254.169.254/metadata/instance?api-version=2021-02-01\" | jq -r '.compute.resourceId')\nINSTANCE_ID=$(echo $BEFORE_INSTANCE_ID | awk -F'/' '{print $NF}')\nRESOURCE_ID=/subscriptions/$SUBSCRIPTION_ID/resourcegroups/$RESOURCE_GROUP_NAME/providers/Microsoft.Compute/virtualMachineScaleSets/$VM_SCALE_SET_NAME\n\nTIMESTAMP=$(date -u +\"%Y-%m-%dT%H:%M:%SZ\")\naz tag update --resource-id $RESOURCE_ID --operation replace --tags \"STATUS\"=\"HEALTHY\" \"InstanceDeleteTime\"=\"$TIMESTAMP\" \"storageAccount\"=\"${storageAccountName}\"\n\naz vmss delete-instances --resource-group $RESOURCE_GROUP_NAME --name $VM_SCALE_SET_NAME --instance-ids $INSTANCE_ID\n", "userDataMediaNodeTemplate": "#!/bin/bash -x\nset -eu -o pipefail\n\n# Introduce the scripts in the instance\n# install.sh\necho ${base64install} | base64 -d > /usr/local/bin/install.sh\nchmod +x /usr/local/bin/install.sh\n\n# stop_media_nodes.sh\necho ${base64stop} | base64 -d > /usr/local/bin/stop_media_node.sh\nchmod +x /usr/local/bin/stop_media_node.sh\n\napt-get update && apt-get install -y \napt-get install -y jq\n\n# Install azure cli\ncurl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash\n\naz login --identity\n\n# Protect from scale in actions\nRESOURCE_GROUP_NAME=${resourceGroupName}\nVM_SCALE_SET_NAME=${vmScaleSetName}\nBEFORE_INSTANCE_ID=$(curl -H Metadata:true --noproxy \"*\" \"http://169.254.169.254/metadata/instance?api-version=2021-02-01\" | jq -r '.compute.resourceId')\nINSTANCE_ID=$(echo $BEFORE_INSTANCE_ID | awk -F'/' '{print $NF}')\naz vmss update --resource-group $RESOURCE_GROUP_NAME --name $VM_SCALE_SET_NAME --instance-id $INSTANCE_ID --protect-from-scale-in true\n\nexport HOME=\"/root\"\n\n# Install OpenVidu\n/usr/local/bin/install.sh || { echo \"[OpenVidu] error installing OpenVidu\"; exit 1; }\n\n# Start OpenVidu\nsystemctl start openvidu || { echo \"[OpenVidu] error starting OpenVidu\"; exit 1; }\n", @@ -618,6 +612,7 @@ "subnetAddressPrefixMedia": "10.0.0.0/24", "vNetName": "[format('{0}-virtualNetwork', parameters('stackName'))]" }, + "isEmptyStorageAccountName": "[equals(parameters('storageAccountName'), '')]", "isEmptyContainerName": "[equals(parameters('containerName'), '')]" }, "resources": [ @@ -879,7 +874,7 @@ "adminPassword": "[parameters('adminSshKey')]", "linuxConfiguration": "[variables('masterNodeVMSettings').linuxConfiguration]" }, - "userData": "[base64(variables('userDataMasterNode4'))]" + "userData": "[base64(reduce(items(createObject('base64install', variables('base64installMaster4'), 'base64after_install', variables('base64after_installMaster'), 'base64update_config_from_secret', variables('base64update_config_from_secretMaster'), 'base64update_secret_from_config', variables('base64update_secret_from_configMaster'), 'base64get_value_from_config', variables('base64get_value_from_configMaster'), 'base64store_secret', variables('base64store_secretMaster'), 'base64check_app_ready', variables('base64check_app_readyMaster'), 'base64restart', variables('base64restartMaster'), 'keyVaultName', variables('keyVaultName'), 'masterNodeNum', '4', 'storageAccountName', if(variables('isEmptyStorageAccountName'), uniqueString(resourceGroup().id), uniqueString(resourceGroup().id)), 'base64config_blobStorage', base64(reduce(items(createObject('storageAccountName', if(variables('isEmptyStorageAccountName'), uniqueString(resourceGroup().id), uniqueString(resourceGroup().id)), 'storageAccountKey', listKeys(resourceId('Microsoft.Storage/storageAccounts', uniqueString(resourceGroup().id)), '2021-04-01').keys[0].value, 'storageAccountContainerName', if(variables('isEmptyContainerName'), 'openvidu-appdata', format('{0}', parameters('containerName'))))), createObject('value', variables('config_blobStorageTemplate')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value))), createObject('value', variables('userDataTemplateMasterNode')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value)]" }, "dependsOn": [ "[resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface4', parameters('stackName')))]", @@ -894,7 +889,7 @@ "location": "[variables('location')]", "tags": { "InstanceDeleteTime": "[parameters('datetime')]", - "storageAccount": "[uniqueString(resourceGroup().id)]" + "storageAccount": "[if(variables('isEmptyStorageAccountName'), uniqueString(resourceGroup().id), uniqueString(resourceGroup().id))]" }, "identity": { "type": "SystemAssigned" @@ -962,7 +957,7 @@ } ] }, - "userData": "[base64(reduce(items(createObject('base64install', base64(reduce(items(createObject('privateIPMasterNode1', reference(resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface1', parameters('stackName'))), '2023-11-01').ipConfigurations[0].properties.privateIPAddress, 'privateIPMasterNode2', reference(resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface2', parameters('stackName'))), '2023-11-01').ipConfigurations[0].properties.privateIPAddress, 'privateIPMasterNode3', reference(resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface3', parameters('stackName'))), '2023-11-01').ipConfigurations[0].properties.privateIPAddress, 'privateIPMasterNode4', reference(resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface4', parameters('stackName'))), '2023-11-01').ipConfigurations[0].properties.privateIPAddress, 'keyVaultName', variables('keyVaultName'))), createObject('value', variables('installScriptTemplateMedia')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64stop', variables('base64stopMediaNode'))), createObject('value', variables('userDataMediaNodeTemplate')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value)]" + "userData": "[base64(reduce(items(createObject('base64install', base64(reduce(items(createObject('privateIPMasterNode1', reference(resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface1', parameters('stackName'))), '2023-11-01').ipConfigurations[0].properties.privateIPAddress, 'privateIPMasterNode2', reference(resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface2', parameters('stackName'))), '2023-11-01').ipConfigurations[0].properties.privateIPAddress, 'privateIPMasterNode3', reference(resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface3', parameters('stackName'))), '2023-11-01').ipConfigurations[0].properties.privateIPAddress, 'privateIPMasterNode4', reference(resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface4', parameters('stackName'))), '2023-11-01').ipConfigurations[0].properties.privateIPAddress, 'keyVaultName', variables('keyVaultName'))), createObject('value', variables('installScriptTemplateMedia')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64stop', variables('base64stopMediaNode'), 'resourceGroupName', resourceGroup().name, 'vmScaleSetName', format('{0}-mediaNodeScaleSet', parameters('stackName')))), createObject('value', variables('userDataMediaNodeTemplate')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value)]" } }, "dependsOn": [ @@ -2433,6 +2428,7 @@ ] }, { + "condition": "[equals(variables('isEmptyStorageAccountName'), true())]", "type": "Microsoft.Storage/storageAccounts", "apiVersion": "2023-01-01", "name": "[uniqueString(resourceGroup().id)]", @@ -2447,6 +2443,7 @@ } }, { + "condition": "[equals(variables('isEmptyStorageAccountName'), true())]", "type": "Microsoft.Storage/storageAccounts/blobServices/containers", "apiVersion": "2023-01-01", "name": "[format('{0}/default/automation-locks', uniqueString(resourceGroup().id))]", @@ -2458,6 +2455,7 @@ ] }, { + "condition": "[equals(variables('isEmptyStorageAccountName'), true())]", "type": "Microsoft.Storage/storageAccounts/blobServices/containers", "apiVersion": "2023-01-01", "name": "[if(variables('isEmptyContainerName'), format('{0}/default/openvidu-appdata', uniqueString(resourceGroup().id)), format('{0}/default/{1}', uniqueString(resourceGroup().id), parameters('containerName')))]",