Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
0c7881f
Enhance address space handling to accept numeric CIDR masks and updat…
Oct 27, 2025
3511980
update version
Oct 27, 2025
68c8df1
add tests
Oct 28, 2025
f0dfda6
docs
Oct 28, 2025
cfb16e6
Merge branch 'main' into jc-wk/address-space-size-cidr
JC-wk Nov 4, 2025
3a4e2a3
bump api version
JC-wk Nov 4, 2025
441f7f2
Merge branch 'main' into jc-wk/address-space-size-cidr
JC-wk Nov 26, 2025
5982d1e
Merge branch 'main' into jc-wk/address-space-size-cidr
JC-wk Dec 24, 2025
bcbbe71
Merge branch 'main' into jc-wk/address-space-size-cidr
JC-wk Jan 9, 2026
8717e98
bump api version
Jan 9, 2026
df6a1c9
Merge branch 'main' into jc-wk/address-space-size-cidr
JC-wk Feb 6, 2026
e234c81
Merge branch 'main' into jc-wk/address-space-size-cidr
JC-wk Mar 31, 2026
3964e93
update api version
Mar 31, 2026
9f5b2aa
Merge branch 'main' into jc-wk/address-space-size-cidr
JC-wk May 22, 2026
1fe1834
update version
May 22, 2026
88ed0dd
e.g.
May 22, 2026
a72674e
trim whitespace
May 22, 2026
a133e15
add tests and improve descriptions
May 22, 2026
64afc10
fix lint issue
May 22, 2026
a398823
Merge branch 'main' into jc-wk/address-space-size-cidr
JC-wk Jun 9, 2026
90e2ad9
Merge branch 'main' into jc-wk/address-space-size-cidr
JC-wk Jun 24, 2026
2330f29
Merge branch 'main' into jc-wk/address-space-size-cidr
JC-wk Jun 30, 2026
87e3f39
Merge branch 'main' into jc-wk/address-space-size-cidr
Jul 3, 2026
c25fc65
Bump version to 0.25.27
JC-wk Jul 3, 2026
985a72f
Merge branch 'main' into jc-wk/address-space-size-cidr
JC-wk Jul 3, 2026
182bf26
Merge branch 'main' into jc-wk/address-space-size-cidr
JC-wk Jul 21, 2026
15231a4
PR feedback - Handle "None" (defaults to small)
Jul 22, 2026
466fe37
None, or empty/whitespace values default to "small" (/24)
Jul 22, 2026
64c1ebd
update workspace templates
Jul 22, 2026
881f496
update template
Jul 22, 2026
31e45ce
remove try catch
Jul 22, 2026
a1f7fea
Adding a parametrized test for both None and "" to prevent regressions.
Jul 22, 2026
59d8b40
Merge branch 'main' into jc-wk/address-space-size-cidr
JC-wk Jul 22, 2026
1c6f9d8
version bump
Jul 22, 2026
ece7709
update descriptions
Jul 22, 2026
f532f39
typo
Jul 22, 2026
d1179d2
copilot suggestion
JC-wk Jul 22, 2026
c4f6318
copilot suggestion
JC-wk Jul 22, 2026
466e743
update description
Jul 22, 2026
9007553
Merge branch 'jc-wk/address-space-size-cidr' of https://github.com/mi…
Jul 22, 2026
92a629c
changelog
Jul 23, 2026
6c9a44b
Merge branch 'main' into jc-wk/address-space-size-cidr
rudolphjacksonm Jul 23, 2026
3a1ca5f
update wording to match templates
Jul 23, 2026
334fc29
Merge branch 'main' into jc-wk/address-space-size-cidr
JC-wk Jul 30, 2026
304ac30
Merge branch 'main' into jc-wk/address-space-size-cidr
JC-wk Jul 31, 2026
1d8cb5e
Merge branch 'main' into jc-wk/address-space-size-cidr
JC-wk Aug 4, 2026
53398a0
increase minor version instead of patch version
Aug 4, 2026
e1282ee
feat: enhance address space validation and update documentation for C…
Aug 4, 2026
bca3646
docs
Aug 4, 2026
8280ea2
typo
Aug 4, 2026
e2d44e3
Merge branch 'main' into jc-wk/address-space-size-cidr
JC-wk Aug 10, 2026
4753b93
Merge branch 'main' into jc-wk/address-space-size-cidr
JC-wk Aug 17, 2026
8fb7769
Merge branch 'main' into jc-wk/address-space-size-cidr
JC-wk Aug 17, 2026
38e67df
Update CHANGELOG.md with new enhancements and fixes
JC-wk Aug 17, 2026
04f4cf4
Track pending workspace address spaces during service uninstall
Aug 21, 2026
d367fef
Remove pending workspace address space tracking
Aug 21, 2026
4e6df3c
Revert "Remove pending workspace address space tracking"
Aug 21, 2026
a6838f6
Revert "Track pending workspace address spaces during service uninstall"
Aug 21, 2026
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
**BREAKING CHANGES**

ENHANCEMENTS:
* Allow numeric CIDR masks in `address_space_size` (e.g. "23") when requesting auto-assigned address spaces; accepts numeric strings and validates the mask range. ([#4733](https://github.com/microsoft/AzureTRE/issues/4733))

## (0.29.0) (August 14, 2026)
**BREAKING CHANGES**
Expand Down
2 changes: 1 addition & 1 deletion api_app/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.26.5"
__version__ = "0.27.0"
5 changes: 4 additions & 1 deletion api_app/api/routes/workspaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,10 @@ async def create_workspace_service(response: Response, workspace_service_input:
# check workspace has address_spaces property
if not workspace.properties.get("address_spaces"):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=strings.WORKSPACE_DOES_NOT_HAVE_ADDRESS_SPACES_PROPERTY)
workspace_service.properties["address_space"] = await workspace_repo.get_address_space_based_on_size(workspace_service_input.properties)
try:
workspace_service.properties["address_space"] = await workspace_repo.get_address_space_based_on_size(workspace_service_input.properties)
except InvalidInput as e:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=str(e))
workspace_patch = ResourcePatch()
workspace_patch.properties = {"address_spaces": workspace.properties["address_spaces"] + [workspace_service.properties["address_space"]]}
# IP address allocation is managed by the API. Ideally this request would happen as a result of the workspace
Expand Down
24 changes: 19 additions & 5 deletions api_app/db/repositories/workspaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,18 +160,32 @@ 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()
raw_size = workspace_properties.get("address_space_size")
if raw_size is None or str(raw_size).strip() == "":
address_space_size = "small"
else:
address_space_size = str(raw_size).strip().lower()

Comment thread
JC-wk marked this conversation as resolved.
# 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)
cidr_netmask = WorkspaceRepository.predefined_address_spaces.get(address_space_size, 24)
return await self.get_new_address_space(cidr_netmask)
# If a numeric cidr was provided (e.g. as a string like "25"), accept it
if address_space_size.isdecimal():
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)

if address_space_size in WorkspaceRepository.predefined_address_spaces:
cidr_netmask = WorkspaceRepository.predefined_address_spaces[address_space_size]
return await self.get_new_address_space(cidr_netmask)

raise InvalidInput(f"Invalid 'address_space_size': {address_space_size}")

# 772 check that the provided address_space is available in the network.
async def validate_address_space(self, address_space):
Expand Down
4 changes: 2 additions & 2 deletions api_app/models/schemas/workspace_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,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="Network address size as a CIDR value or (small /24, medium /22, large /16 or custom with an IP range e.g. 10.2.1.0/25) to be used by the workspace.")
},
Comment thread
JC-wk marked this conversation as resolved.
customActions=[
CustomAction()
Expand Down Expand Up @@ -72,7 +72,7 @@ class WorkspaceTemplateInCreate(ResourceTemplateInCreate):
"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": "Network address size as a CIDR value or (small /24, medium /22, large /16 or custom with an IP range e.g. 10.2.1.0/25) to be used by the workspace."
},
"address_space": {
"type": "string",
Expand Down
20 changes: 19 additions & 1 deletion api_app/tests_ma/test_api/test_routes/test_workspaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from models.domain.resource_template import ResourceTemplate
from models.schemas.operation import OperationInResponse

from db.errors import EntityDoesNotExist, StorageAccountNameGenerationTimeout, StorageAccountNameCheckFailed
from db.errors import EntityDoesNotExist, InvalidInput, StorageAccountNameGenerationTimeout, StorageAccountNameCheckFailed
from db.repositories.workspaces import WorkspaceRepository
from db.repositories.workspace_services import WorkspaceServiceRepository
from models.domain.authentication import RoleAssignment
Expand Down Expand Up @@ -803,6 +803,24 @@ async def test_post_workspace_services_creates_workspace_service_with_address_sp
assert response.status_code == status.HTTP_202_ACCEPTED
assert response.json()["operation"]["resourceId"] == SERVICE_ID

# [POST] /workspaces/{workspace_id}/workspace-services
@patch("api.dependencies.workspaces.WorkspaceRepository.get_address_space_based_on_size", side_effect=InvalidInput("'address_space_size' numeric value must be between 16 and 29"))
@patch("api.dependencies.workspaces.WorkspaceRepository.get_workspace_by_id")
@patch("api.routes.workspaces.OperationRepository.resource_has_deployed_operation", return_value=True)
@patch("api.routes.workspaces.WorkspaceServiceRepository.create_workspace_service_item")
async def test_post_workspace_services_returns_422_for_invalid_address_space_size(self, create_workspace_service_item_mock, _, get_workspace_mock, __, app, client, workspace_service_input, basic_workspace_service_template):
workspace = sample_workspace()
workspace.properties["address_spaces"] = ["192.168.0.1/24"]
get_workspace_mock.return_value = workspace
basic_workspace_service_template.properties["address_space"] = "10.1.0.0/24"
create_workspace_service_item_mock.return_value = [sample_workspace_service(), basic_workspace_service_template]
workspace_service_input["properties"]["address_space_size"] = "15"

response = await client.post(app.url_path_for(strings.API_CREATE_WORKSPACE_SERVICE, workspace_id=WORKSPACE_ID), json=workspace_service_input)

assert response.status_code == status.HTTP_422_UNPROCESSABLE_CONTENT
Comment thread
JC-wk marked this conversation as resolved.
assert response.text == "'address_space_size' numeric value must be between 16 and 29"

# [POST] /workspaces/{workspace_id}/workspace-services
@patch("api.dependencies.workspaces.WorkspaceRepository.get_new_address_space", return_value="10.1.4.0/24")
@patch("api.routes.workspaces.ResourceTemplateRepository.get_template_by_name_and_version")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,3 +414,69 @@ async def test_is_workspace_storage_account_available_when_check_times_out(mock_
await workspace_repo.is_workspace_storage_account_available(MagicMock(), workspace_id)

assert mock_storage_client_instance.storage_accounts.check_name_availability.call_count == 1


@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')
Comment thread
JC-wk marked this conversation as resolved.


@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')


@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"


@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("empty_size", [None, "", " "])
async def test_get_address_space_based_on_size_with_none_or_empty_address_space_size(workspace_repo, basic_workspace_request, empty_size):
workspace_to_create = basic_workspace_request
workspace_to_create.properties["address_space_size"] = empty_size
assert "10.1.4.0/24" == await workspace_repo.get_address_space_based_on_size(workspace_to_create.properties)


@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_preset", ["huge", "extra_large", "invalid"])
async def test_get_address_space_based_on_size_with_unrecognized_preset_raises_error(workspace_repo, basic_workspace_request, invalid_preset):
workspace_to_create = basic_workspace_request
workspace_to_create.properties["address_space_size"] = invalid_preset
with pytest.raises(InvalidInput) as ex:
await workspace_repo.get_address_space_based_on_size(workspace_to_create.properties)
assert str(ex.value) == f"Invalid 'address_space_size': {invalid_preset}"
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,18 @@ The mandatory parameters for workspace services are:
| `tre_id` | string | Unique ID of for the TRE instance. | `tre-dev-42` |
| `workspace_id` | string | Unique 4-character long, alphanumeric workspace ID. | `0a9e` |

### Workpace services requiring additional address spaces
### Workspace services requiring additional address spaces

Some workspace services may require additional address spaces to be provisioned. This may be necessary if they need advanced network security groups, route tables or delegated subnets.

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.
The API has support for allocating CIDR subnet masks from "16" to "29". Workspace templates are configured to support from "16" (65,536 IP addresses) to "24" (256 IP addresses).
Depending on the workspace service you are deploying you may configure a template with a CIDR up to "29" which has only 3 usable IP addresses as Azure reserves the first four and last address of every subnet.
The workspace service IP addresses are in addition to the workspace addresses and the address size is unrelated to the workspace IP address size.
The workspace will be patched to add the additional address space to its vnet.

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.

Expand Down
2 changes: 1 addition & 1 deletion templates/workspaces/airlock-import-review/porter.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-workspace-airlock-import-review
version: 0.16.1
version: 0.17.0
description: "A workspace to do Airlock Data Import Reviews for Azure TRE"
dockerfile: Dockerfile.tmpl
registry: azuretre
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,18 @@
"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": "Network address size as a CIDR value or (small /24, medium /22, large /16 or custom with an IP range e.g. 10.2.1.0/25) to be used by the workspace.",
"default": "small",
Comment thread
JC-wk marked this conversation as resolved.
"enum": [
"24",
"23",
"22",
"21",
"20",
"19",
"18",
"17",
"16",
"small",
"medium",
"large",
Expand Down
2 changes: 1 addition & 1 deletion templates/workspaces/base/porter.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-workspace-base
version: 2.10.1
version: 2.11.0
description: "A base Azure TRE workspace"
dockerfile: Dockerfile.tmpl
registry: azuretre
Expand Down
13 changes: 11 additions & 2 deletions templates/workspaces/base/template_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,18 @@
"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": "Network address size as a CIDR value or (small /24, medium /22, large /16 or custom with an IP range e.g. 10.2.1.0/25) to be used by the workspace.",
"default": "small",
Comment thread
JC-wk marked this conversation as resolved.
"enum": [
"24",
"23",
"22",
"21",
"20",
"19",
"18",
"17",
"16",
"small",
"medium",
"large",
Expand Down Expand Up @@ -381,4 +390,4 @@
"*"
]
}
}
}
2 changes: 1 addition & 1 deletion templates/workspaces/unrestricted/porter.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
schemaVersion: 1.0.0
name: tre-workspace-unrestricted
version: 0.14.1
version: 0.15.0
description: "A base Azure TRE workspace"
dockerfile: Dockerfile.tmpl
registry: azuretre
Expand Down
11 changes: 10 additions & 1 deletion templates/workspaces/unrestricted/template_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,18 @@
"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": "Network address size as a CIDR value or (small /24, medium /22, large /16 or custom with an IP range e.g. 10.2.1.0/25) to be used by the workspace.",
"default": "small",
Comment thread
JC-wk marked this conversation as resolved.
"enum": [
"24",
"23",
"22",
"21",
"20",
"19",
"18",
"17",
"16",
"small",
"medium",
"large",
Expand Down
Loading