From 29fbb28ad9309c60bec28e6063513dd8d69325d5 Mon Sep 17 00:00:00 2001 From: Adam <103067949+AdamL-Microsoft@users.noreply.github.com> Date: Wed, 25 May 2022 09:30:38 -0700 Subject: [PATCH] adding function app settings bicep template and updating deploy.py (#1973) * adding function app settings bicep templates and updating deploy.py for toggling function states through enable_dotnet argument * fixes #1948 --- src/deployment/azuredeploy.bicep | 81 +++++++++++++++++++ .../function-settings-disabled-apps.bicep | 55 +++++++++++++ .../bicep-templates/function-settings.bicep | 18 ++++- src/deployment/deploy.py | 76 ++++++++++++++++- 4 files changed, 227 insertions(+), 3 deletions(-) create mode 100644 src/deployment/bicep-templates/function-settings-disabled-apps.bicep diff --git a/src/deployment/azuredeploy.bicep b/src/deployment/azuredeploy.bicep index 138758aa01..af4fdf63b4 100644 --- a/src/deployment/azuredeploy.bicep +++ b/src/deployment/azuredeploy.bicep @@ -24,6 +24,9 @@ param diagnosticsLogLevel string = 'Verbose' var log_retention = 30 var tenantId = subscription().tenantId +var python_functions_disabled = '0' +var dotnet_functions_disabled = '1' + var scaleset_identity = '${name}-scalesetid' var StorageBlobDataReader = '2a2b9908-6ea1-4ae2-8e65-a410df84e7d1' @@ -261,6 +264,45 @@ module pythonFunctionSettings 'bicep-templates/function-settings.bicep' = { keyvault_name: keyVaultName monitor_account_name: operationalInsights.outputs.monitorAccountName multi_tenant_domain: multi_tenant_domain + functions_disabled: python_functions_disabled + all_function_names: [ + 'agent_can_schedule' //0 + 'agent_commands' //1 + 'agent_events' //2 + 'agent_registration' //3 + 'containers' //4 + 'download' //5 + 'info' //6 + 'instance_config' //7 + 'jobs' //8 + 'job_templates' //9 + 'job_templates_manage' //10 + 'negotiate' //11 + 'node' //12 + 'node_add_ssh_key' //13 + 'notifications' //14 + 'pool' //15 + 'proxy' //16 + 'queue_file_changes' //17 + 'queue_node_heartbeat' //18 + 'queue_proxy_update' //19 + 'queue_signalr_events' //20 + 'queue_task_heartbeat' //21 + 'queue_updates' //22 + 'queue_webhooks' //23 + 'repro_vms' //24 + 'scaleset' //25 + 'tasks' //26 + 'timer_daily' //27 + 'timer_proxy' //28 + 'timer_repro' //29 + 'timer_retention' //30 + 'timer_tasks' //31 + 'timer_workers' //32 + 'webhooks' //33 + 'webhooks_logs' //34 + 'webhooks_ping' //35 + ] } dependsOn: [ pythonFunction @@ -286,6 +328,45 @@ module netFunctionSettings 'bicep-templates/function-settings.bicep' = { keyvault_name: keyVaultName monitor_account_name: operationalInsights.outputs.monitorAccountName multi_tenant_domain: multi_tenant_domain + functions_disabled: dotnet_functions_disabled + all_function_names: [ + 'AgentCanSchedule' //0 + 'AgentCommands' //1 + 'AgentEvents' //2 + 'AgentRegistration' //3 + 'Containers' //4 + 'Download' //5 + 'Info' //6 + 'InstanceConfig' //7 + 'Jobs' //8 + 'JobTemplates' //9 + 'JobTemplatesManage' //10 + 'Negotiate' //11 + 'Node' //12 + 'NodeAddSshKey' //13 + 'Notifications' //14 + 'Pool' //15 + 'Proxy' //16 + 'QueueFileChanges' //17 + 'QueueNodeHeartbeat' //18 + 'QueueProxyUpdate' //19 + 'QueueSignalrEvents' //20 + 'QueueTaskHeartbeat' //21 + 'QueueUpdates' //22 + 'QueueWebhooks' //23 + 'ReproVms' //24 + 'Scaleset' //25 + 'Tasks' //26 + 'TimerDaily' //27 + 'TimerProxy' //28 + 'TimerRepro' //29 + 'TimerRetention' //30 + 'TimerTasks' //31 + 'TimerWorkers' //32 + 'Webhooks' //33 + 'WebhooksLogs' //34 + 'WebhooksPing' //35 + ] } dependsOn: [ netFunction diff --git a/src/deployment/bicep-templates/function-settings-disabled-apps.bicep b/src/deployment/bicep-templates/function-settings-disabled-apps.bicep new file mode 100644 index 0000000000..20e13059da --- /dev/null +++ b/src/deployment/bicep-templates/function-settings-disabled-apps.bicep @@ -0,0 +1,55 @@ +param functions_disabled_setting string + +param allFunctions array + +var disabledFunctions = [for f in allFunctions: 'AzureWebJobs.${f}.Disabled' ] + +var disabledFunctionsAppSettings = { + '${disabledFunctions[0]}' : functions_disabled_setting + '${disabledFunctions[1]}' : functions_disabled_setting + '${disabledFunctions[2]}' : functions_disabled_setting + '${disabledFunctions[3]}' : functions_disabled_setting + '${disabledFunctions[4]}' : functions_disabled_setting + + '${disabledFunctions[5]}' : functions_disabled_setting + '${disabledFunctions[6]}' : functions_disabled_setting + '${disabledFunctions[7]}' : functions_disabled_setting + '${disabledFunctions[8]}' : functions_disabled_setting + '${disabledFunctions[9]}' : functions_disabled_setting + + '${disabledFunctions[10]}' : functions_disabled_setting + '${disabledFunctions[11]}' : functions_disabled_setting + '${disabledFunctions[12]}' : functions_disabled_setting + '${disabledFunctions[13]}' : functions_disabled_setting + '${disabledFunctions[14]}' : functions_disabled_setting + + '${disabledFunctions[15]}' : functions_disabled_setting + '${disabledFunctions[16]}' : functions_disabled_setting + '${disabledFunctions[17]}' : functions_disabled_setting + '${disabledFunctions[18]}' : functions_disabled_setting + '${disabledFunctions[19]}' : functions_disabled_setting + + '${disabledFunctions[20]}' : functions_disabled_setting + '${disabledFunctions[21]}' : functions_disabled_setting + '${disabledFunctions[22]}' : functions_disabled_setting + '${disabledFunctions[23]}' : functions_disabled_setting + '${disabledFunctions[24]}' : functions_disabled_setting + + '${disabledFunctions[25]}' : functions_disabled_setting + '${disabledFunctions[26]}' : functions_disabled_setting + '${disabledFunctions[27]}' : functions_disabled_setting + '${disabledFunctions[28]}' : functions_disabled_setting + '${disabledFunctions[29]}' : functions_disabled_setting + + '${disabledFunctions[30]}' : functions_disabled_setting + '${disabledFunctions[31]}' : functions_disabled_setting + '${disabledFunctions[32]}' : functions_disabled_setting + '${disabledFunctions[33]}' : functions_disabled_setting + '${disabledFunctions[34]}' : functions_disabled_setting + + '${disabledFunctions[35]}' : functions_disabled_setting +} + +output functions array = disabledFunctions +output appSettings object = disabledFunctionsAppSettings + diff --git a/src/deployment/bicep-templates/function-settings.bicep b/src/deployment/bicep-templates/function-settings.bicep index 1a6203781b..9a40890eba 100644 --- a/src/deployment/bicep-templates/function-settings.bicep +++ b/src/deployment/bicep-templates/function-settings.bicep @@ -26,16 +26,30 @@ param monitor_account_name string param functions_worker_runtime string param functions_extension_version string +param functions_disabled string + +param all_function_names array + +var disabledFunctionName = 'disabledFunctions-${functions_worker_runtime}' + var telemetry = 'd7a73cf4-5a1a-4030-85e1-e5b25867e45a' resource function 'Microsoft.Web/sites@2021-02-01' existing = { name: name } +module disabledFunctions 'function-settings-disabled-apps.bicep' = { + name: disabledFunctionName + params:{ + functions_disabled_setting: functions_disabled + allFunctions: all_function_names + } +} + resource functionSettings 'Microsoft.Web/sites/config@2021-03-01' = { parent: function name: 'appsettings' - properties: { + properties: union({ 'FUNCTIONS_EXTENSION_VERSION': functions_extension_version 'FUNCTIONS_WORKER_RUNTIME': functions_worker_runtime 'FUNCTIONS_WORKER_PROCESS_COUNT': '1' @@ -56,6 +70,6 @@ resource functionSettings 'Microsoft.Web/sites/config@2021-03-01' = { 'ONEFUZZ_KEYVAULT': keyvault_name 'ONEFUZZ_OWNER': owner 'ONEFUZZ_CLIENT_SECRET': client_secret - } + }, disabledFunctions.outputs.appSettings) } diff --git a/src/deployment/deploy.py b/src/deployment/deploy.py index 91741ab964..c7f05f4d30 100644 --- a/src/deployment/deploy.py +++ b/src/deployment/deploy.py @@ -152,6 +152,7 @@ def __init__( subscription_id: Optional[str], admins: List[UUID], allowed_aad_tenants: List[UUID], + enable_dotnet: List[str], ): self.subscription_id = subscription_id self.resource_group = resource_group @@ -186,6 +187,8 @@ def __init__( self.arm_template = bicep_to_arm(bicep_template) + self.enable_dotnet = enable_dotnet + machine = platform.machine() system = platform.system() @@ -1065,6 +1068,67 @@ def deploy_dotnet_app(self) -> None: if error is not None: raise error + def enable_dotnet_func(self) -> None: + if self.enable_dotnet: + func = shutil.which("az") + assert func is not None + for function_name in self.enable_dotnet: + format_name = function_name.split("_") + dotnet_name = "".join(x.title() for x in format_name) + error: Optional[subprocess.CalledProcessError] = None + max_tries = 5 + for i in range(max_tries): + try: + # disable python function + logger.info(f"disabling PYTHON function: {function_name}") + subprocess.check_output( + [ + func, + "functionapp", + "config", + "appsettings", + "set", + "--name", + self.application_name, + "--resource-group", + self.application_name, + "--settings", + f"AzureWebJobs.{function_name}.Disabled=1", + ], + env=dict(os.environ, CLI_DEBUG="1"), + ) + # enable dotnet function + logger.info(f"enabling DOTNET function: {dotnet_name}") + subprocess.check_output( + [ + func, + "functionapp", + "config", + "appsettings", + "set", + "--name", + self.application_name + "-net", + "--resource-group", + self.application_name, + "--settings", + f"AzureWebJobs.{dotnet_name}.Disabled=0", + ], + env=dict(os.environ, CLI_DEBUG="1"), + ) + break + except subprocess.CalledProcessError as err: + error = err + if i + 1 < max_tries: + logger.debug("func failure error: %s", err) + logger.warning( + f"{function_name} function didn't respond to " + "status change request, 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 @@ -1128,6 +1192,7 @@ def main() -> None: ("dotnet-api", Client.deploy_dotnet_app), ("export_appinsights", Client.add_log_export), ("update_registration", Client.update_registration), + ("enable_dotnet", Client.enable_dotnet_func), ] formatter = argparse.ArgumentDefaultsHelpFormatter @@ -1238,7 +1303,15 @@ def main() -> None: nargs="*", help="Set additional AAD tenants beyond the tenant the app is deployed in", ) - + parser.add_argument( + "--enable_dotnet", + type=str, + nargs="+", + default=[], + help="Provide a space-seperated list of python function names to disable " + "their functions and enable corresponding dotnet functions in the Azure " + "Function App deployment", + ) args = parser.parse_args() if shutil.which("func") is None: @@ -1268,6 +1341,7 @@ def main() -> None: subscription_id=args.subscription_id, admins=args.set_admins, allowed_aad_tenants=args.allowed_aad_tenants or [], + enable_dotnet=args.enable_dotnet, ) if args.verbose: level = logging.DEBUG