Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions src/azure-cli/azure/cli/command_modules/appservice/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
ASE_KINDS = ['ASEv2', 'ASEv3']
ASE_OS_PREFERENCE_TYPES = ['Windows', 'Linux']
PUBLIC_NETWORK_ACCESS_MODES = ['Enabled', 'Disabled']
DAPR_LOG_LEVELS = ['debug', 'error', 'info', 'warn']


# pylint: disable=too-many-statements, too-many-lines
Expand Down Expand Up @@ -397,8 +398,15 @@ def load_arguments(self, _):
help='the container registry server username')
c.argument('registry_password', options_list=['--registry-password', '-p', c.deprecate(target='--docker-registry-server-password', redirect='--registry-password')],
help='the container registry server password')
c.argument('min_replicas', type=int, help="The minimum number of replicas when create funtion app on container app", is_preview=True)
c.argument('max_replicas', type=int, help="The maximum number of replicas when create funtion app on container app", is_preview=True)
c.argument('min_replicas', type=int, help="The minimum number of replicas when create function app on container app", is_preview=True)
c.argument('max_replicas', type=int, help="The maximum number of replicas when create function app on container app", is_preview=True)
c.argument('enable_dapr', help="Enable/Disable Dapr for a function app on an Azure Container App environment", arg_type=get_three_state_flag(return_label=True))
c.argument('dapr_app_id', help="The Dapr application identifier.")
c.argument('dapr_app_port', type=int, help="The port Dapr uses to communicate to the application.")
c.argument('dapr_http_max_request_size', type=int, options_list=['--dapr-http-max-request-size', '--dhmrs'], help="Max size of request body http and grpc servers in MB to handle uploading of large files.")
c.argument('dapr_http_read_buffer_size', type=int, options_list=['--dapr-http-read-buffer-size', '--dhrbs'], help="Max size of http header read buffer in KB to handle when sending multi-KB headers.")
c.argument('dapr_log_level', help="The log level for the Dapr sidecar", arg_type=get_enum_type(DAPR_LOG_LEVELS))
c.argument('dapr_enable_api_logging', options_list=['--dapr-enable-api-logging', '--dal'], help="Enable/Disable API logging for the Dapr sidecar.", arg_type=get_three_state_flag(return_label=True))

with self.argument_context('webapp config connection-string list') as c:
c.argument('name', arg_type=webapp_name_arg_type, id_part=None)
Expand Down Expand Up @@ -737,8 +745,15 @@ def load_arguments(self, _):
c.argument('registry_username', options_list=['--registry-username', '-d', c.deprecate(target='--docker-registry-server-user', redirect='--registry-username')], help='The container registry server username.')
c.argument('registry_password', options_list=['--registry-password', '-w', c.deprecate(target='--docker-registry-server-password', redirect='--registry-password')],
help='The container registry server password. Required for private registries.')
c.argument('min_replicas', type=int, help="The minimum number of replicas when create funtion app on container app", is_preview=True)
c.argument('max_replicas', type=int, help="The maximum number of replicas when create funtion app on container app", is_preview=True)
c.argument('min_replicas', type=int, help="The minimum number of replicas when create function app on container app", is_preview=True)
c.argument('max_replicas', type=int, help="The maximum number of replicas when create function app on container app", is_preview=True)
c.argument('enable_dapr', help="Enable/Disable Dapr for a function app on an Azure Container App environment", arg_type=get_three_state_flag(return_label=True))
c.argument('dapr_app_id', help="The Dapr application identifier.")
c.argument('dapr_app_port', type=int, help="The port Dapr uses to communicate to the application.")
c.argument('dapr_http_max_request_size', type=int, options_list=['--dapr-http-max-request-size', '--dhmrs'], help="Max size of request body http and grpc servers in MB to handle uploading of large files.")
c.argument('dapr_http_read_buffer_size', type=int, options_list=['--dapr-http-read-buffer-size', '--dhrbs'], help="Max size of http header read buffer in KB to handle when sending multi-KB headers.")
c.argument('dapr_log_level', help="The log level for the Dapr sidecar", arg_type=get_enum_type(DAPR_LOG_LEVELS))
c.argument('dapr_enable_api_logging', options_list=['--dapr-enable-api-logging', '--dal'], help="Enable/Disable API logging for the Dapr sidecar.", arg_type=get_three_state_flag(return_label=True))
c.argument('workspace', help="Name of an existing log analytics workspace to be used for the application insights component")

with self.argument_context('functionapp cors credentials') as c:
Expand Down
71 changes: 67 additions & 4 deletions src/azure-cli/azure/cli/command_modules/appservice/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -1780,7 +1780,14 @@ def update_container_settings(cmd, resource_group_name, name, docker_registry_se

def update_container_settings_functionapp(cmd, resource_group_name, name, registry_server=None,
image=None, registry_username=None,
registry_password=None, slot=None, min_replicas=None, max_replicas=None):
registry_password=None, slot=None, min_replicas=None, max_replicas=None,
enable_dapr=None, dapr_app_id=None, dapr_app_port=None,
dapr_http_max_request_size=None, dapr_http_read_buffer_size=None,
dapr_log_level=None, dapr_enable_api_logging=None):
if is_centauri_functionapp(cmd, resource_group_name, name):
update_dapr_config(cmd, resource_group_name, name, enable_dapr, dapr_app_id, dapr_app_port,
dapr_http_max_request_size, dapr_http_read_buffer_size, dapr_log_level,
dapr_enable_api_logging)
return update_container_settings(cmd, resource_group_name, name, registry_server,
image, registry_username, None,
registry_password, multicontainer_config_type=None,
Expand Down Expand Up @@ -3756,6 +3763,39 @@ def should_enable_distributed_tracing(consumption_plan_location, matched_runtime
and image is None


def update_functionapp_polling(cmd, resource_group_name, name, functionapp):
try:
_generic_site_operation(cmd.cli_ctx, resource_group_name, name, 'update', None, functionapp)
except Exception as ex: # pylint: disable=broad-except
poll_url = ex.response.headers['Location'] if 'Location' in ex.response.headers else None
if ex.response.status_code == 202 and poll_url:
r = send_raw_request(cmd.cli_ctx, method='get', url=poll_url)
poll_timeout = time.time() + 60 * 2 # 2 minute timeout

while r.status_code != 200 and time.time() < poll_timeout:
time.sleep(5)
r = send_raw_request(cmd.cli_ctx, method='get', url=poll_url)
else:
raise CLIError(ex)


def update_dapr_config(cmd, resource_group_name, name, enabled=None, app_id=None, app_port=None,
http_max_request_size=None, http_read_buffer_size=None, log_level=None,
enable_api_logging=None):
site = _generic_site_operation(cmd.cli_ctx, resource_group_name, name, 'get')
import inspect
frame = inspect.currentframe()
bool_flags = ['enabled', 'enable_api_logging']
int_flags = ['app_port', 'http_max_request_size', 'http_read_buffer_size']
args, _, _, values = inspect.getargvalues(frame) # pylint: disable=deprecated-method
for arg in args[3:]:
if arg in int_flags and values[arg] is not None:
values[arg] = validate_and_convert_to_int(arg, values[arg])
if values.get(arg, None):
setattr(site.dapr_config, arg, values[arg] if arg not in bool_flags else values[arg] == 'true')
update_functionapp_polling(cmd, resource_group_name, name, site)


def create_functionapp(cmd, resource_group_name, name, storage_account, plan=None,
os_type=None, functions_version=None, runtime=None, runtime_version=None,
consumption_plan_location=None, app_insights=None, app_insights_key=None,
Expand All @@ -3764,7 +3804,9 @@ def create_functionapp(cmd, resource_group_name, name, storage_account, plan=Non
registry_server=None, registry_password=None, registry_username=None,
image=None, tags=None, assign_identities=None,
role='Contributor', scope=None, vnet=None, subnet=None, https_only=False,
environment=None, min_replicas=None, max_replicas=None, workspace=None):
environment=None, min_replicas=None, max_replicas=None, workspace=None,
enable_dapr=False, dapr_app_id=None, dapr_app_port=None, dapr_http_max_request_size=None,
dapr_http_read_buffer_size=None, dapr_log_level=None, dapr_enable_api_logging=False):
# pylint: disable=too-many-statements, too-many-branches
if functions_version is None:
logger.warning("No functions version specified so defaulting to 3. In the future, specifying a version will "
Expand All @@ -3779,13 +3821,23 @@ def create_functionapp(cmd, resource_group_name, name, storage_account, plan=Non
raise RequiredArgumentMissingError("usage error: parameters --min-replicas and --max-replicas must be "
"used with parameter --environment, please provide the name "
"of the container app environment using --environment.")
if any([enable_dapr, dapr_app_id, dapr_app_port, dapr_http_max_request_size, dapr_http_read_buffer_size,
dapr_log_level, dapr_enable_api_logging]) and environment is None:
raise RequiredArgumentMissingError("usage error: parameters --enable-dapr, --dapr-app-id, "
"--dapr-app-port, --dapr-http-max-request-size, "
"--dapr-http-read-buffer-size, --dapr-log-level and "
"dapr-enable-api-logging must be used with parameter --environment,"
"please provide the name of the container app environment using "
"--environment.")
from azure.mgmt.web.models import Site
SiteConfig, NameValuePair = cmd.get_models('SiteConfig', 'NameValuePair')
SiteConfig, NameValuePair, DaprConfig = cmd.get_models('SiteConfig', 'NameValuePair', 'DaprConfig')
disable_app_insights = (disable_app_insights == "true")

site_config = SiteConfig(app_settings=[])
client = web_client_factory(cmd.cli_ctx)

dapr_config = DaprConfig()

if vnet or subnet:
if plan:
if is_valid_resource_id(plan):
Expand Down Expand Up @@ -3815,7 +3867,7 @@ def create_functionapp(cmd, resource_group_name, name, storage_account, plan=Non
subnet_resource_id = None
vnet_route_all_enabled = None

functionapp_def = Site(location=None, site_config=site_config, tags=tags,
functionapp_def = Site(location=None, site_config=site_config, dapr_config=dapr_config, tags=tags,
virtual_network_subnet_id=subnet_resource_id, https_only=https_only,
vnet_route_all_enabled=vnet_route_all_enabled)

Expand Down Expand Up @@ -3975,6 +4027,17 @@ def create_functionapp(cmd, resource_group_name, name, storage_account, plan=Non
if max_replicas is not None:
site_config.function_app_scale_limit = max_replicas

if enable_dapr:
logger.warning("Please note while using Dapr Extension for Azure Functions, app port is "
"mandatory when using Dapr triggers and should be empty when using only Dapr bindings.")
dapr_config.enabled = True
dapr_config.app_id = dapr_app_id
dapr_config.app_port = dapr_app_port
dapr_config.http_max_request_size = dapr_http_max_request_size
dapr_config.http_read_buffer_size = dapr_http_read_buffer_size
dapr_config.log_level = dapr_log_level
dapr_config.enable_api_logging = dapr_enable_api_logging

managed_environment = get_managed_environment(cmd, resource_group_name, environment)
location = managed_environment.location
functionapp_def.location = location
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,59 @@ def test_functionapp_consumption_linux_powershell(self, resource_group, storage_
self.cmd('functionapp config show -g {} -n {}'.format(resource_group, functionapp_name), checks=[
JMESPathCheck('linuxFxVersion', 'PowerShell|7.2')])


class FunctionappDapr(LiveScenarioTest):
Copy link
Contributor Author

@kamperiadis kamperiadis Dec 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: had to keep this test in live mode because it would fail otherwise as it would somehow not find 'operationresults' polling matches in the recording but would always succeed in live mode.

Copy link
Contributor Author

@kamperiadis kamperiadis Dec 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

@AllowLargeResponse(8192)
@ResourceGroupPreparer(location="northeurope")
@StorageAccountPreparer()
def test_functionapp_dapr_config_e2e(self, resource_group, storage_account):
functionapp_name = self.create_random_name(
'functionappdapr', 24)
managed_environment_name = self.create_random_name(
'managedenvironment', 40
)

self.cmd('containerapp env create --name {} --resource-group {} --location {} --logs-destination none'.format(
managed_environment_name,
resource_group,
"northeurope"
))

self.cmd('functionapp create -g {} -n {} -s {} --environment {} --dapr-app-id daprappid --dapr-app-port 800 --dhmrs 4 --dhrbs 50 --dapr-log-level debug --enable-dapr true --functions-version 4'.format(
resource_group,
functionapp_name,
storage_account,
managed_environment_name
)).assert_with_checks([
JMESPathCheck('daprConfig.enabled', True),
JMESPathCheck('daprConfig.appId', 'daprappid'),
JMESPathCheck('daprConfig.appPort', 800),
JMESPathCheck('daprConfig.httpReadBufferSize', 50),
JMESPathCheck('daprConfig.httpMaxRequestSize', 4),
JMESPathCheck('daprConfig.logLevel', 'debug'),
JMESPathCheck('daprConfig.enableApiLogging', False)
])

time.sleep(1200)

self.cmd('functionapp config container set -g {} -n {} --dapr-app-id daprappid1 --dapr-app-port 80 --dal --dhmrs 6 --dhrbs 60 --dapr-log-level warn --enable-dapr false'.format(
resource_group,
functionapp_name
))

time.sleep(1200)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this 1200 just be on safer side that the functionapp config command will be finished by then?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, otherwise, we get a 409 error since the previous operation is not done


self.cmd('functionapp show -g {} -n {}'.format(resource_group, functionapp_name)).assert_with_checks([
JMESPathCheck('daprConfig.enabled', False),
JMESPathCheck('daprConfig.appId', 'daprappid1'),
JMESPathCheck('daprConfig.appPort', 80),
JMESPathCheck('daprConfig.httpReadBufferSize', 60),
JMESPathCheck('daprConfig.httpMaxRequestSize', 6),
JMESPathCheck('daprConfig.logLevel', 'warn'),
JMESPathCheck('daprConfig.enableApiLogging', True)
])


class FunctionAppManagedEnvironment(LiveScenarioTest):
@ResourceGroupPreparer(location=WINDOWS_ASP_LOCATION_FUNCTIONAPP)
@StorageAccountPreparer()
Expand Down