diff --git a/src/deployment/azuredeploy.bicep b/src/deployment/azuredeploy.bicep index 0e36af7fb..138758aa0 100644 --- a/src/deployment/azuredeploy.bicep +++ b/src/deployment/azuredeploy.bicep @@ -162,7 +162,7 @@ module eventGrid 'bicep-templates/event-grid.bicep' = { ] } -// try to make role assignments to deploy as late as possible in order to has principalId ready +// try to make role assignments to deploy as late as possible in order to have principalId ready resource roleAssigmentsPy 'Microsoft.Authorization/roleAssignments@2020-10-01-preview' = [for r in roleAssignmentsParams: { name: guid('${resourceGroup().id}${r.suffix}-python') properties: { @@ -176,7 +176,7 @@ resource roleAssigmentsPy 'Microsoft.Authorization/roleAssignments@2020-10-01-pr ] }] -// try to make role assignments to deploy as late as possible in order to has principalId ready +// try to make role assignments to deploy as late as possible in order to have principalId ready resource roleAssigmentsNet 'Microsoft.Authorization/roleAssignments@2020-10-01-preview' = [for r in roleAssignmentsParams: { name: guid('${resourceGroup().id}${r.suffix}-net') properties: { @@ -191,7 +191,7 @@ resource roleAssigmentsNet 'Microsoft.Authorization/roleAssignments@2020-10-01-p }] -// try to make role assignments to deploy as late as possible in order to has principalId ready +// try to make role assignments to deploy as late as possible in order to have principalId ready resource readBlobUserAssignment 'Microsoft.Authorization/roleAssignments@2020-10-01-preview' = { name: guid('${resourceGroup().id}-user_managed_idenity_read_blob') properties: { @@ -209,63 +209,87 @@ resource readBlobUserAssignment 'Microsoft.Authorization/roleAssignments@2020-10 module pythonFunction 'bicep-templates/function.bicep' = { name: 'pythonFunction' params: { - functions_worker_runtime: 'python' - linux_fx_version: 'Python|3.8' - functions_extension_version: '~3' name: name + linux_fx_version: 'Python|3.8' - instance_name: name app_logs_sas_url: storage.outputs.FuncSasUrlBlobAppLogs app_func_audiences: app_func_audiences app_func_issuer: app_func_issuer - app_insights_app_id: operationalInsights.outputs.appInsightsAppId - app_insights_key: operationalInsights.outputs.appInsightsInstrumentationKey - client_id: clientId - client_secret: clientSecret + diagnostics_log_level: diagnosticsLogLevel - func_sas_url: storage.outputs.FuncSasUrl - func_storage_resource_id: storage.outputs.FuncId - fuzz_storage_resource_id: storage.outputs.FuzzId - keyvault_name: keyVaultName location: location log_retention: log_retention - monitor_account_name: operationalInsights.outputs.monitorAccountName - multi_tenant_domain: multi_tenant_domain owner: owner server_farm_id: serverFarms.outputs.id - signal_r_connection_string: signalR.outputs.connectionString + client_id: clientId } } module netFunction 'bicep-templates/function.bicep' = { name: 'netFunction' params: { - functions_worker_runtime: 'dotnet-isolated' linux_fx_version: 'DOTNET-ISOLATED|6.0' - functions_extension_version: '~4' name: '${name}-net' - instance_name: name app_logs_sas_url: storage.outputs.FuncSasUrlBlobAppLogs app_func_audiences: app_func_audiences app_func_issuer: app_func_issuer + client_id: clientId + diagnostics_log_level: diagnosticsLogLevel + location: location + log_retention: log_retention + owner: owner + server_farm_id: serverFarms.outputs.id + } +} + +module pythonFunctionSettings 'bicep-templates/function-settings.bicep' = { + name: 'pythonFunctionSettings' + params: { + name: name + owner: owner + functions_worker_runtime: 'python' + functions_extension_version: '~3' + instance_name: name app_insights_app_id: operationalInsights.outputs.appInsightsAppId app_insights_key: operationalInsights.outputs.appInsightsInstrumentationKey - client_id: clientId client_secret: clientSecret - diagnostics_log_level: diagnosticsLogLevel + signal_r_connection_string: signalR.outputs.connectionString func_sas_url: storage.outputs.FuncSasUrl func_storage_resource_id: storage.outputs.FuncId fuzz_storage_resource_id: storage.outputs.FuzzId keyvault_name: keyVaultName - location: location - log_retention: log_retention monitor_account_name: operationalInsights.outputs.monitorAccountName multi_tenant_domain: multi_tenant_domain - owner: owner - server_farm_id: serverFarms.outputs.id - signal_r_connection_string: signalR.outputs.connectionString } + dependsOn: [ + pythonFunction + ] +} + + +module netFunctionSettings 'bicep-templates/function-settings.bicep' = { + name: 'netFunctionSettings' + params: { + owner: owner + name: '${name}-net' + signal_r_connection_string: signalR.outputs.connectionString + app_insights_app_id: operationalInsights.outputs.appInsightsAppId + app_insights_key: operationalInsights.outputs.appInsightsInstrumentationKey + functions_worker_runtime: 'dotnet-isolated' + functions_extension_version: '~4' + instance_name: name + client_secret: clientSecret + func_sas_url: storage.outputs.FuncSasUrl + func_storage_resource_id: storage.outputs.FuncId + fuzz_storage_resource_id: storage.outputs.FuzzId + keyvault_name: keyVaultName + monitor_account_name: operationalInsights.outputs.monitorAccountName + multi_tenant_domain: multi_tenant_domain + } + dependsOn: [ + netFunction + ] } diff --git a/src/deployment/bicep-templates/function-settings.bicep b/src/deployment/bicep-templates/function-settings.bicep new file mode 100644 index 000000000..1a6203781 --- /dev/null +++ b/src/deployment/bicep-templates/function-settings.bicep @@ -0,0 +1,61 @@ +param name string +param instance_name string +param owner string +param app_insights_app_id string +@secure() +param app_insights_key string + +@secure() +param func_sas_url string + +param multi_tenant_domain string + +@secure() +param signal_r_connection_string string + +param func_storage_resource_id string +param fuzz_storage_resource_id string + +param keyvault_name string + +@secure() +param client_secret string + +param monitor_account_name string + +param functions_worker_runtime string +param functions_extension_version string + +var telemetry = 'd7a73cf4-5a1a-4030-85e1-e5b25867e45a' + +resource function 'Microsoft.Web/sites@2021-02-01' existing = { + name: name +} + +resource functionSettings 'Microsoft.Web/sites/config@2021-03-01' = { + parent: function + name: 'appsettings' + properties: { + 'FUNCTIONS_EXTENSION_VERSION': functions_extension_version + 'FUNCTIONS_WORKER_RUNTIME': functions_worker_runtime + 'FUNCTIONS_WORKER_PROCESS_COUNT': '1' + 'APPINSIGHTS_INSTRUMENTATIONKEY': app_insights_key + 'APPINSIGHTS_APPID': app_insights_app_id + 'ONEFUZZ_TELEMETRY': telemetry + 'AzureWebJobsStorage': func_sas_url + 'MULTI_TENANT_DOMAIN': multi_tenant_domain + 'AzureWebJobsDisableHomepage': 'true' + 'AzureSignalRConnectionString': signal_r_connection_string + 'AzureSignalRServiceTransportType': 'Transient' + 'ONEFUZZ_INSTANCE_NAME': instance_name + 'ONEFUZZ_INSTANCE': 'https://${name}.azurewebsites.net' + 'ONEFUZZ_RESOURCE_GROUP': resourceGroup().id + 'ONEFUZZ_DATA_STORAGE': fuzz_storage_resource_id + 'ONEFUZZ_FUNC_STORAGE': func_storage_resource_id + 'ONEFUZZ_MONITOR': monitor_account_name + 'ONEFUZZ_KEYVAULT': keyvault_name + 'ONEFUZZ_OWNER': owner + 'ONEFUZZ_CLIENT_SECRET': client_secret + } +} + diff --git a/src/deployment/bicep-templates/function.bicep b/src/deployment/bicep-templates/function.bicep index c5f3b2beb..54936f3e4 100644 --- a/src/deployment/bicep-templates/function.bicep +++ b/src/deployment/bicep-templates/function.bicep @@ -1,5 +1,4 @@ param name string -param instance_name string param location string param owner string @@ -11,18 +10,6 @@ param app_func_audiences array @secure() param app_logs_sas_url string -param app_insights_app_id string -@secure() -param app_insights_key string - -@secure() -param func_sas_url string - -param multi_tenant_domain string - -@secure() -param signal_r_connection_string string - @description('The degree of severity for diagnostics logs.') @allowed([ 'Verbose' @@ -32,22 +19,8 @@ param signal_r_connection_string string ]) param diagnostics_log_level string param log_retention int - -param func_storage_resource_id string -param fuzz_storage_resource_id string - -param keyvault_name string - -@secure() -param client_secret string - -param monitor_account_name string - param linux_fx_version string -param functions_worker_runtime string -param functions_extension_version string -var telemetry = 'd7a73cf4-5a1a-4030-85e1-e5b25867e45a' resource function 'Microsoft.Web/sites@2021-03-01' = { name: name @@ -123,31 +96,4 @@ resource funcLogs 'Microsoft.Web/sites/config@2021-03-01' = { parent: function } -resource pythonFunctionSettings 'Microsoft.Web/sites/config@2021-03-01' = { - name: 'appsettings' - parent: function - properties: { - 'FUNCTIONS_EXTENSION_VERSION': functions_extension_version - 'FUNCTIONS_WORKER_RUNTIME': functions_worker_runtime - 'FUNCTIONS_WORKER_PROCESS_COUNT': '1' - 'APPINSIGHTS_INSTRUMENTATIONKEY': app_insights_key - 'APPINSIGHTS_APPID': app_insights_app_id - 'ONEFUZZ_TELEMETRY': telemetry - 'AzureWebJobsStorage': func_sas_url - 'MULTI_TENANT_DOMAIN': multi_tenant_domain - 'AzureWebJobsDisableHomepage': 'true' - 'AzureSignalRConnectionString': signal_r_connection_string - 'AzureSignalRServiceTransportType': 'Transient' - 'ONEFUZZ_INSTANCE_NAME': instance_name - 'ONEFUZZ_INSTANCE': 'https://${name}.azurewebsites.net' - 'ONEFUZZ_RESOURCE_GROUP': resourceGroup().id - 'ONEFUZZ_DATA_STORAGE': fuzz_storage_resource_id - 'ONEFUZZ_FUNC_STORAGE': func_storage_resource_id - 'ONEFUZZ_MONITOR': monitor_account_name - 'ONEFUZZ_KEYVAULT': keyvault_name - 'ONEFUZZ_OWNER': owner - 'ONEFUZZ_CLIENT_SECRET': client_secret - } -} - output principalId string = reference(function.id, function.apiVersion, 'Full').identity.principalId diff --git a/src/deployment/deploy.py b/src/deployment/deploy.py index cb5e6b3df..618eaa846 100644 --- a/src/deployment/deploy.py +++ b/src/deployment/deploy.py @@ -138,6 +138,7 @@ class Client: client_id: Optional[str], client_secret: Optional[str], app_zip: str, + app_net_zip: str, tools: str, instance_specific: str, third_party: str, @@ -159,6 +160,7 @@ class Client: self.owner = owner self.nsg_config = nsg_config self.app_zip = app_zip + self.app_net_zip = app_net_zip self.tools = tools self.instance_specific = instance_specific self.third_party = third_party @@ -1026,6 +1028,43 @@ class Client: if error is not None: raise error + def deploy_dotnet_app(self) -> None: + logger.info("deploying function app %s ", self.app_net_zip) + with tempfile.TemporaryDirectory() as tmpdirname: + with zipfile.ZipFile(self.app_net_zip, "r") as zip_ref: + func = shutil.which("func") + assert func is not None + + zip_ref.extractall(tmpdirname) + error: Optional[subprocess.CalledProcessError] = None + max_tries = 5 + for i in range(max_tries): + try: + subprocess.check_output( + [ + func, + "azure", + "functionapp", + "publish", + self.application_name + "-net", + "--no-build", + ], + env=dict(os.environ, CLI_DEBUG="1"), + cwd=tmpdirname, + ) + return + except subprocess.CalledProcessError as err: + error = err + if i + 1 < max_tries: + logger.debug("func failure error: %s", err) + logger.warning( + "function failed to deploy, waiting 60 " + "seconds and trying again" + ) + time.sleep(60) + if error is not None: + raise error + def update_registration(self) -> None: if not self.create_registration: return @@ -1115,6 +1154,12 @@ def main() -> None: default="api-service.zip", help="(default: %(default)s)", ) + parser.add_argument( + "--app-net-zip", + type=arg_file, + default="api-service-net.zip", + help="(default: %(default)s)", + ) parser.add_argument( "--tools", type=arg_dir, default="tools", help="(default: %(default)s)" ) @@ -1192,6 +1237,11 @@ def main() -> None: nargs="*", help="Set additional AAD tenants beyond the tenant the app is deployed in", ) + parser.add_argument( + "--dotnet_deploy", + action="store_true", + help="deploys the dotnet version of the app along with the python version", + ) args = parser.parse_args() @@ -1208,6 +1258,7 @@ def main() -> None: client_id=args.client_id, client_secret=args.client_secret, app_zip=args.app_zip, + app_net_zip=args.app_net_zip, tools=args.tools, instance_specific=args.instance_specific, third_party=args.third_party, @@ -1238,6 +1289,12 @@ def main() -> None: ) states = rbac_only_states else: + if args.dotnet_deploy: + logger.info("deploying dotnet and python services for Azure functions") + after_python = full_deployment_states.index(("api", Client.deploy_app)) + 1 + full_deployment_states.insert( + after_python, ("dotnet-api", Client.deploy_dotnet_app) + ) states = full_deployment_states if args.start_at != states[0][0]: