From 0c7881fdb656bcb0cf2b59b7c4870b44285e4730 Mon Sep 17 00:00:00 2001 From: James Chapman Date: Mon, 27 Oct 2025 19:15:56 +0000 Subject: [PATCH 01/12] Enhance address space handling to accept numeric CIDR masks and update documentation accordingly --- CHANGELOG.md | 1 + api_app/db/repositories/workspaces.py | 16 ++++++++++++++-- api_app/models/schemas/workspace_template.py | 2 +- .../authoring-workspace-templates.md | 2 +- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 111b4513b3..088975aa69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ ENHANCEMENTS: * Update oauth2-proxy and Tomcat versions to latest in Guacamole container ([#4688](https://github.com/microsoft/AzureTRE/pull/4688)) * Standardize database query parameter handling across repository classes ([#4697](https://github.com/microsoft/AzureTRE/issues/4697)) * Improve documentation on `address_space` allocation ([#4726](https://github.com/microsoft/AzureTRE/pull/4726)) +* Allow numeric CIDR masks in `address_space_size` (e.g. "25") when requesting auto-assigned address spaces; accepts numeric strings and validates the mask range. ([#4733](https://github.com/microsoft/AzureTRE/issues/4733)) BUG FIXES: * Remove deprecated ms-teams-notification action from E2E test workflows ([#4716](https://github.com/microsoft/AzureTRE/issues/4716)) diff --git a/api_app/db/repositories/workspaces.py b/api_app/db/repositories/workspaces.py index 5be9cab979..36f366f2fb 100644 --- a/api_app/db/repositories/workspaces.py +++ b/api_app/db/repositories/workspaces.py @@ -142,13 +142,25 @@ async def get_address_space_based_on_size(self, workspace_properties: dict): address_space_size = workspace_properties.get("address_space_size", "small").lower() # 773 allow custom sized networks to be requested - if (address_space_size == "custom"): + if address_space_size == "custom": if (await self.validate_address_space(workspace_properties.get("address_space"))): return workspace_properties.get("address_space") else: raise InvalidInput("The custom 'address_space' you requested does not fit in the current network.") - # Default mask is 24 (small) + # If a numeric cidr was provided (e.g as a string like "25"), accept it + try: + if address_space_size.isdigit(): + cidr_netmask = int(address_space_size) + # basic validation for reasonable CIDR mask values + if cidr_netmask < 16 or cidr_netmask > 29: + raise InvalidInput("'address_space_size' numeric value must be between 16 and 29") + return await self.get_new_address_space(cidr_netmask) + except ValueError: + # fall through to predefined handling + pass + + # Default mask is 24 (small). Keep backwards compatibility with presets. cidr_netmask = WorkspaceRepository.predefined_address_spaces.get(address_space_size, 24) return await self.get_new_address_space(cidr_netmask) diff --git a/api_app/models/schemas/workspace_template.py b/api_app/models/schemas/workspace_template.py index bc20955217..0b3fcd31e0 100644 --- a/api_app/models/schemas/workspace_template.py +++ b/api_app/models/schemas/workspace_template.py @@ -22,7 +22,7 @@ def get_sample_workspace_template_object(template_name: str = "tre-workspace-bas "address_space_size": Property( type="string", default="small", - description="This can have a value of small, medium, large or custom. If you specify custom, then you need to specify a VNet address space in 'address_space' (e.g. 10.2.1.0/24)") + description="This can have a value of small, medium, large, a numeric CIDR mask (e.g. 25) or custom. If you specify custom, then you need to specify a VNet address space in 'address_space' (e.g. 10.2.1.0/24)") }, customActions=[ CustomAction() diff --git a/docs/tre-workspace-authors/authoring-workspace-templates.md b/docs/tre-workspace-authors/authoring-workspace-templates.md index 23614bb29f..7fca854056 100644 --- a/docs/tre-workspace-authors/authoring-workspace-templates.md +++ b/docs/tre-workspace-authors/authoring-workspace-templates.md @@ -105,7 +105,7 @@ Some workspace services may require additional address spaces to be provisioned. To request an additional address space, the workspace service bundle must define an `address_space` parameter in the `porter.yaml` file. The value of this parameter will be provided by API to the resource processor. -The size of the `address_space` will default to `/24`, however other sizes can be requested by including an `address_space_size` as part of the workspace service template. +The size of the `address_space` will default to `/24`, however other sizes can be requested by including an `address_space_size` as part of the workspace service template. This parameter accepts the presets `small` (/24), `medium` (/22), `large` (/16), the literal value `custom` together with an explicit `address_space` CIDR (e.g. `10.2.1.0/25`), or a numeric CIDR mask as a string (e.g. `"25"`) to ask the system to auto-select an available `/25`. The `address_space` allocation will only take place during the install phase of a deployment, as this is a breaking change to your template you should increment the major version of your template, this means a you must deploy a new resource instead of upgrading an existing one. From 3511980eac60d1bc17196a8f404c757f1b706f35 Mon Sep 17 00:00:00 2001 From: James Chapman Date: Mon, 27 Oct 2025 19:25:33 +0000 Subject: [PATCH 02/12] update version --- api_app/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api_app/_version.py b/api_app/_version.py index 6fea77c010..71290cd566 100644 --- a/api_app/_version.py +++ b/api_app/_version.py @@ -1 +1 @@ -__version__ = "0.25.0" +__version__ = "0.25.1" From 68c8df123ec090e4c99c551f0c42c95907006bbf Mon Sep 17 00:00:00 2001 From: James Chapman Date: Tue, 28 Oct 2025 09:01:59 +0000 Subject: [PATCH 03/12] add tests --- .../test_workpaces_repository.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/api_app/tests_ma/test_db/test_repositories/test_workpaces_repository.py b/api_app/tests_ma/test_db/test_repositories/test_workpaces_repository.py index a72b2d85e1..1eea120a12 100644 --- a/api_app/tests_ma/test_db/test_repositories/test_workpaces_repository.py +++ b/api_app/tests_ma/test_db/test_repositories/test_workpaces_repository.py @@ -330,3 +330,29 @@ async def test_is_workspace_storage_account_available_when_name_not_available(mo mock_storage_client.return_value.storage_accounts.check_name_availability.assert_called_once_with({"name": f"stgws{workspace_id[-4:]}"}) assert result is False + + +@pytest.mark.asyncio +@patch('core.config.RESOURCE_LOCATION', "useast2") +@patch('core.config.TRE_ID', "9876") +@patch('core.config.CORE_ADDRESS_SPACE', "10.1.0.0/22") +@patch('core.config.TRE_ADDRESS_SPACE', "10.0.0.0/12") +async def test_get_address_space_based_on_size_with_string_19(workspace_repo, basic_workspace_request): + workspace_to_create = basic_workspace_request + # request a /19 + workspace_to_create.properties["address_space_size"] = "19" + address_space = await workspace_repo.get_address_space_based_on_size(workspace_to_create.properties) + assert address_space.endswith('/19') + + +@pytest.mark.asyncio +@patch('core.config.RESOURCE_LOCATION', "useast2") +@patch('core.config.TRE_ID', "9876") +@patch('core.config.CORE_ADDRESS_SPACE', "10.1.0.0/22") +@patch('core.config.TRE_ADDRESS_SPACE', "10.0.0.0/12") +async def test_get_address_space_based_on_size_with_string_29(workspace_repo, basic_workspace_request): + workspace_to_create = basic_workspace_request + # request a /29 + workspace_to_create.properties["address_space_size"] = "29" + address_space = await workspace_repo.get_address_space_based_on_size(workspace_to_create.properties) + assert address_space.endswith('/29') From f0dfda65ce6ec08196294a295491e97b61e379b5 Mon Sep 17 00:00:00 2001 From: James Chapman Date: Tue, 28 Oct 2025 09:03:41 +0000 Subject: [PATCH 04/12] docs --- docs/tre-workspace-authors/authoring-workspace-templates.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/tre-workspace-authors/authoring-workspace-templates.md b/docs/tre-workspace-authors/authoring-workspace-templates.md index 7fca854056..48e9f7a24c 100644 --- a/docs/tre-workspace-authors/authoring-workspace-templates.md +++ b/docs/tre-workspace-authors/authoring-workspace-templates.md @@ -105,7 +105,8 @@ Some workspace services may require additional address spaces to be provisioned. To request an additional address space, the workspace service bundle must define an `address_space` parameter in the `porter.yaml` file. The value of this parameter will be provided by API to the resource processor. -The size of the `address_space` will default to `/24`, however other sizes can be requested by including an `address_space_size` as part of the workspace service template. This parameter accepts the presets `small` (/24), `medium` (/22), `large` (/16), the literal value `custom` together with an explicit `address_space` CIDR (e.g. `10.2.1.0/25`), or a numeric CIDR mask as a string (e.g. `"25"`) to ask the system to auto-select an available `/25`. +The size of the `address_space` will default to `/24`, however other sizes can be requested by including an `address_space_size` as part of the workspace service template. +This parameter accepts the presets `small` (/24), `medium` (/22), `large` (/16), the literal value `custom` together with an explicit `address_space` CIDR (e.g. `10.2.1.0/25`), or a numeric CIDR mask as a string from "16" to "29" (e.g. `"25"`) to ask the system to auto-select an available `/25`. The `address_space` allocation will only take place during the install phase of a deployment, as this is a breaking change to your template you should increment the major version of your template, this means a you must deploy a new resource instead of upgrading an existing one. From 3a4e2a3af8a2a6a8a518daef3ce7606f408cdd15 Mon Sep 17 00:00:00 2001 From: JC-wk <196318169+JC-wk@users.noreply.github.com> Date: Tue, 4 Nov 2025 10:08:31 +0000 Subject: [PATCH 05/12] bump api version --- api_app/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api_app/_version.py b/api_app/_version.py index 71290cd566..97b0e6226c 100644 --- a/api_app/_version.py +++ b/api_app/_version.py @@ -1 +1 @@ -__version__ = "0.25.1" +__version__ = "0.25.2" From 8717e98858f88975b233f7f41695164f9de6941b Mon Sep 17 00:00:00 2001 From: James Chapman Date: Fri, 9 Jan 2026 10:21:43 +0000 Subject: [PATCH 06/12] bump api version --- api_app/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api_app/_version.py b/api_app/_version.py index 5461be1190..4f3cae6929 100644 --- a/api_app/_version.py +++ b/api_app/_version.py @@ -1 +1 @@ -__version__ = "0.25.7" +__version__ = "0.25.8" From 3964e9342861693328606966437a0222c00804e7 Mon Sep 17 00:00:00 2001 From: James Chapman Date: Tue, 31 Mar 2026 15:51:58 +0000 Subject: [PATCH 07/12] update api version --- api_app/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api_app/_version.py b/api_app/_version.py index 2cb28789f2..7923a95d33 100644 --- a/api_app/_version.py +++ b/api_app/_version.py @@ -1 +1 @@ -__version__ = "0.25.15" +__version__ = "0.25.16" From 1fe1834ecc902dadead7604cc727822c5da4e602 Mon Sep 17 00:00:00 2001 From: James Chapman Date: Fri, 22 May 2026 15:20:55 +0000 Subject: [PATCH 08/12] update version --- api_app/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api_app/_version.py b/api_app/_version.py index 7923a95d33..57a322dfc2 100644 --- a/api_app/_version.py +++ b/api_app/_version.py @@ -1 +1 @@ -__version__ = "0.25.16" +__version__ = "0.25.17" From 88ed0dd0fc793fdbe8665c13f04ad5e80d5db27f Mon Sep 17 00:00:00 2001 From: James Chapman Date: Fri, 22 May 2026 15:30:28 +0000 Subject: [PATCH 09/12] e.g. --- api_app/db/repositories/workspaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api_app/db/repositories/workspaces.py b/api_app/db/repositories/workspaces.py index 72c434e0f9..2e1c4ce3fb 100644 --- a/api_app/db/repositories/workspaces.py +++ b/api_app/db/repositories/workspaces.py @@ -149,7 +149,7 @@ async def get_address_space_based_on_size(self, workspace_properties: dict): else: raise InvalidInput("The custom 'address_space' you requested does not fit in the current network.") - # If a numeric cidr was provided (e.g as a string like "25"), accept it + # If a numeric cidr was provided (e.g. as a string like "25"), accept it try: if address_space_size.isdigit(): cidr_netmask = int(address_space_size) From a72674e095cc69a42c1f0542cb7da76d251d7cbd Mon Sep 17 00:00:00 2001 From: James Chapman Date: Fri, 22 May 2026 15:32:09 +0000 Subject: [PATCH 10/12] trim whitespace --- api_app/db/repositories/workspaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api_app/db/repositories/workspaces.py b/api_app/db/repositories/workspaces.py index 2e1c4ce3fb..26db14d337 100644 --- a/api_app/db/repositories/workspaces.py +++ b/api_app/db/repositories/workspaces.py @@ -140,7 +140,7 @@ def automatically_create_application_registration(self, workspace_properties: di async def get_address_space_based_on_size(self, workspace_properties: dict): # Default the address space to 'small' if not supplied. - address_space_size = workspace_properties.get("address_space_size", "small").lower() + address_space_size = workspace_properties.get("address_space_size", "small").strip().lower() # 773 allow custom sized networks to be requested if address_space_size == "custom": From a133e1571e21ee10fff111178837b97176a61c2a Mon Sep 17 00:00:00 2001 From: James Chapman Date: Fri, 22 May 2026 15:48:51 +0000 Subject: [PATCH 11/12] add tests and improve descriptions --- api_app/models/schemas/workspace_template.py | 4 ++-- .../test_repositories/test_workpaces_repository.py | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/api_app/models/schemas/workspace_template.py b/api_app/models/schemas/workspace_template.py index 0b3fcd31e0..9d68a3f1fd 100644 --- a/api_app/models/schemas/workspace_template.py +++ b/api_app/models/schemas/workspace_template.py @@ -22,7 +22,7 @@ def get_sample_workspace_template_object(template_name: str = "tre-workspace-bas "address_space_size": Property( type="string", default="small", - description="This can have a value of small, medium, large, a numeric CIDR mask (e.g. 25) or custom. If you specify custom, then you need to specify a VNet address space in 'address_space' (e.g. 10.2.1.0/24)") + description="This can have a value of small, medium, large, a numeric CIDR mask (e.g. \"25\") or custom. If you specify custom, then you need to specify a VNet address space in 'address_space' (e.g. 10.2.1.0/24)") }, customActions=[ CustomAction() @@ -73,7 +73,7 @@ class Config: "address_space_size": { "type": "string", "title": "Address space size", - "description": "Network address size (small, medium, large or custom) to be used by the workspace" + "description": "This can have a value of small, medium, large, a numeric CIDR mask (e.g. \"25\") or custom. If you specify custom, then you need to specify a VNet address space in 'address_space' (e.g. 10.2.1.0/24)" }, "address_space": { "type": "string", diff --git a/api_app/tests_ma/test_db/test_repositories/test_workpaces_repository.py b/api_app/tests_ma/test_db/test_repositories/test_workpaces_repository.py index 3df91cdf3a..5e592cbea9 100644 --- a/api_app/tests_ma/test_db/test_repositories/test_workpaces_repository.py +++ b/api_app/tests_ma/test_db/test_repositories/test_workpaces_repository.py @@ -368,3 +368,16 @@ async def test_get_address_space_based_on_size_with_string_29(workspace_repo, ba workspace_to_create.properties["address_space_size"] = "29" address_space = await workspace_repo.get_address_space_based_on_size(workspace_to_create.properties) assert address_space.endswith('/29') + +@pytest.mark.asyncio +@patch('core.config.RESOURCE_LOCATION', "useast2") +@patch('core.config.TRE_ID', "9876") +@patch('core.config.CORE_ADDRESS_SPACE', "10.1.0.0/22") +@patch('core.config.TRE_ADDRESS_SPACE', "10.0.0.0/12") +@pytest.mark.parametrize("invalid_size", ["15", "30"]) +async def test_get_address_space_based_on_size_with_invalid_string_raises_error(workspace_repo, basic_workspace_request, invalid_size): + workspace_to_create = basic_workspace_request + workspace_to_create.properties["address_space_size"] = invalid_size + with pytest.raises(InvalidInput) as ex: + await workspace_repo.get_address_space_based_on_size(workspace_to_create.properties) + assert str(ex.value) == "'address_space_size' numeric value must be between 16 and 29" From 64afc1017a545aed72901bf4fdebbeff3f9c5fe5 Mon Sep 17 00:00:00 2001 From: James Chapman Date: Fri, 22 May 2026 15:57:26 +0000 Subject: [PATCH 12/12] fix lint issue --- .../test_db/test_repositories/test_workpaces_repository.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api_app/tests_ma/test_db/test_repositories/test_workpaces_repository.py b/api_app/tests_ma/test_db/test_repositories/test_workpaces_repository.py index 5e592cbea9..474ab4f3f5 100644 --- a/api_app/tests_ma/test_db/test_repositories/test_workpaces_repository.py +++ b/api_app/tests_ma/test_db/test_repositories/test_workpaces_repository.py @@ -369,6 +369,7 @@ async def test_get_address_space_based_on_size_with_string_29(workspace_repo, ba address_space = await workspace_repo.get_address_space_based_on_size(workspace_to_create.properties) assert address_space.endswith('/29') + @pytest.mark.asyncio @patch('core.config.RESOURCE_LOCATION', "useast2") @patch('core.config.TRE_ID', "9876")