Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability for workspace service to request an address space #2902

Merged
merged 39 commits into from
Dec 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
3b36858
Add ability for workspace service to request an address space
marrobi Nov 23, 2022
03f5a57
Increase API version
marrobi Nov 23, 2022
52fb385
Fix issue in prior PR that added array to an array.
marrobi Nov 23, 2022
4c44c64
remove blank lines
marrobi Nov 23, 2022
9b5fa48
Merge branch 'main' of github.com:microsoft/AzureTRE into marrobi/iss…
marrobi Nov 23, 2022
e3578a0
Public works, private fails 445
marrobi Nov 25, 2022
0659a06
Merge branch 'main' into marrobi/issue2810
tamirkamara Nov 28, 2022
47efdf5
Merge main
marrobi Dec 2, 2022
bf84bf3
Add comment.
marrobi Dec 2, 2022
e5bc1ec
Merge branch 'main' of github.com:microsoft/AzureTRE into marrobi/iss…
marrobi Dec 8, 2022
c7402c3
Add false for force update.
marrobi Dec 8, 2022
4bfb103
Update etag logic
marrobi Dec 9, 2022
c2654cf
Add docs and changelog
marrobi Dec 9, 2022
c816d20
Merge branch 'main' into marrobi/issue2810
marrobi Dec 9, 2022
5b7f541
tf linting issues
marrobi Dec 9, 2022
712491b
Merge branch 'marrobi/issue2810' of github.com:marrobi/AzureTRE into …
marrobi Dec 9, 2022
24edf34
Remove etag from tests
marrobi Dec 9, 2022
cef3700
Remove etag from tests
marrobi Dec 9, 2022
098d368
set etag in test
marrobi Dec 9, 2022
bcfeebd
update assignent
marrobi Dec 9, 2022
9479cbd
change return object for mock
marrobi Dec 9, 2022
959129e
Set etag on modified workspace
marrobi Dec 9, 2022
5c6f9c2
add template version to modified resource
marrobi Dec 9, 2022
1d3ccea
Update templates/workspaces/base/terraform/network/providers.tf
marrobi Dec 12, 2022
42868e4
Update api_app/_version.py
marrobi Dec 12, 2022
66f16db
Update lock file and providers as per comment.
marrobi Dec 12, 2022
727afca
Merge branch 'main' into marrobi/issue2810
marrobi Dec 12, 2022
529f52e
add missing dependancies on az login
marrobi Dec 12, 2022
53441fa
Merge branch 'marrobi/issue2810' of github.com:marrobi/AzureTRE into …
marrobi Dec 12, 2022
6dc6553
Merge branch 'main' into marrobi/issue2810
marrobi Dec 14, 2022
99e6ce2
Merge branch 'main' into marrobi/issue2810
tamirkamara Dec 19, 2022
fc5565f
Merge branch 'main' into marrobi/issue2810
tamirkamara Dec 19, 2022
0de176b
Merge branch 'main' into marrobi/issue2810
tamirkamara Dec 19, 2022
00fe0d1
Merge branch 'main' into marrobi/issue2810
tamirkamara Dec 20, 2022
42badb6
Merge branch 'main' into marrobi/issue2810
tamirkamara Dec 20, 2022
8f93a8c
fix unit tests failing by async cosmos client bug
anatbal Dec 21, 2022
d1ca5bf
Merge branch 'main' into marrobi/issue2810
anatbal Dec 25, 2022
46bc406
Merge branch 'main' into marrobi/issue2810
tamirkamara Dec 25, 2022
3e5e7f8
Merge branch 'main' into marrobi/issue2810
anatbal Dec 25, 2022
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ ENHANCEMENTS:
* Support template version update ([#2908](https://github.com/microsoft/AzureTRE/pull/2908))
* Update docker base images to bullseye ([#2946](https://github.com/microsoft/AzureTRE/pull/2946)
* Support updating the firewall when installing via makefile/CICD ([#2942](https://github.com/microsoft/AzureTRE/pull/2942))
* Add the ability for workspace services to request addional address spaces from a workspace ([#2902](https://github.com/microsoft/AzureTRE/pull/2902))
* Airlock processor function and api app service work with http2
* Added the option to disable Swagger ([#2981](https://github.com/microsoft/AzureTRE/pull/2981))


BUG FIXES:
* Private endpoints for AppInsights are now provisioning successfully and consistently ([#2841](https://github.com/microsoft/AzureTRE/pull/2841))
* Enable upgrade step of base workspace ([#2899](https://github.com/microsoft/AzureTRE/pull/2899))
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.6.6"
__version__ = "0.7.0"
18 changes: 17 additions & 1 deletion api_app/api/routes/workspaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ async def retrieve_workspace_service_by_id(workspace_service=Depends(get_workspa


@workspace_services_workspace_router.post("/workspaces/{workspace_id}/workspace-services", status_code=status.HTTP_202_ACCEPTED, response_model=OperationInResponse, name=strings.API_CREATE_WORKSPACE_SERVICE, dependencies=[Depends(get_current_workspace_owner_user)])
async def create_workspace_service(response: Response, workspace_service_input: WorkspaceServiceInCreate, user=Depends(get_current_workspace_owner_user), workspace_service_repo=Depends(get_repository(WorkspaceServiceRepository)), resource_template_repo=Depends(get_repository(ResourceTemplateRepository)), operations_repo=Depends(get_repository(OperationRepository)), workspace=Depends(get_deployed_workspace_by_id_from_path)) -> OperationInResponse:
async def create_workspace_service(response: Response, workspace_service_input: WorkspaceServiceInCreate, user=Depends(get_current_workspace_owner_user), workspace_service_repo=Depends(get_repository(WorkspaceServiceRepository)), workspace_repo=Depends(get_repository(WorkspaceRepository)), resource_template_repo=Depends(get_repository(ResourceTemplateRepository)), operations_repo=Depends(get_repository(OperationRepository)), workspace=Depends(get_deployed_workspace_by_id_from_path)) -> OperationInResponse:

try:
workspace_service, resource_template = await workspace_service_repo.create_workspace_service_item(workspace_service_input, workspace.id, user.roles)
Expand All @@ -231,6 +231,22 @@ async def create_workspace_service(response: Response, workspace_service_input:
logging.error(f"User not authorized to use template: {e}")
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(e))

# if template has address_space get an address space
if resource_template.properties.get("address_space"):
# 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)
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
# service deployment via the reosurce processor. there is no such functionality so the database is being
# updated directly, and an "update" on the workspace is called by the workspace service pipeline.
try:
await workspace_repo.patch_workspace(workspace, workspace_patch, workspace.etag, resource_template_repo, user, False)
except CosmosAccessConditionFailedError:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=strings.ETAG_CONFLICT)

operation = await save_and_deploy_resource(
resource=workspace_service,
resource_repo=workspace_service_repo,
Expand Down
1 change: 1 addition & 0 deletions api_app/resources/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
WORKSPACE_SERVICE_IS_NOT_DEPLOYED = "Workspace service is not deployed."
WORKSPACE_SERVICE_NEEDS_TO_BE_DISABLED_BEFORE_DELETION = "The workspace service needs to be disabled before you can delete it"
WORKSPACE_SERVICES_NEED_TO_BE_DELETED_BEFORE_WORKSPACE = "All workspace services need to be deleted before you can delete the workspace"
WORKSPACE_DOES_NOT_HAVE_ADDRESS_SPACES_PROPERTY = "Workspace does not have address_spaces property"
WORKSPACE_TEMPLATE_VERSION_EXISTS = "A template with this version already exists"
OPERATION_DOES_NOT_EXIST = "Operation does not exist"
CUSTOM_ACTION_NOT_DEFINED = "The specified custom action isn't defined in the targeted resource."
Expand Down
3 changes: 1 addition & 2 deletions api_app/schemas/workspace.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
"description": "These parameters are required for all workspaces",
"required": [
"display_name",
"description",
"address_space_size"
"description"
],
"properties": {
"display_name": {
Expand Down
55 changes: 55 additions & 0 deletions api_app/tests_ma/test_api/test_routes/test_workspaces.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import uuid
from pydantic import Field
import pytest
from mock import patch

Expand Down Expand Up @@ -652,6 +653,60 @@ async def test_post_workspace_services_creates_workspace_service(self, _, __, __
assert response.status_code == status.HTTP_202_ACCEPTED
assert response.json()["operation"]["resourceId"] == SERVICE_ID

# [POST] /workspaces/{workspace_id}/workspace-services
@ patch("api.routes.workspaces.save_and_deploy_resource", return_value=sample_resource_operation(resource_id=SERVICE_ID, operation_id=OPERATION_ID))
@ patch("api.routes.workspaces.WorkspaceRepository.get_timestamp", return_value=FAKE_UPDATE_TIMESTAMP)
@ patch("api.routes.workspaces.WorkspaceRepository.update_item_with_etag")
@ 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")
@ 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_creates_workspace_service_with_address_space(self, create_workspace_service_item_mock, __, get_workspace_mock, resource_template_repo, ___, update_item_mock, ____, _____, app, client, workspace_service_input, basic_workspace_service_template, basic_resource_template):
etag = "some-etag-value"
workspace = sample_workspace()
workspace.properties["address_spaces"] = ["192.168.0.1/24"]
workspace.etag = etag
get_workspace_mock.return_value = workspace
basic_workspace_service_template.properties["address_space"]: str = Field()
create_workspace_service_item_mock.return_value = [sample_workspace_service(), basic_workspace_service_template]
basic_resource_template.properties["address_spaces"] = {"type": "array", "updateable": True}
resource_template_repo.side_effect = [basic_resource_template, basic_workspace_service_template]

modified_workspace = sample_workspace()
modified_workspace.isEnabled = True
modified_workspace.history = [ResourceHistoryItem(properties={'client_id': '12345', 'scope_id': 'test_scope_id', 'address_spaces': ["192.168.0.1/24"]}, templateVersion='0.1.0', isEnabled=True, resourceVersion=0, updatedWhen=FAKE_CREATE_TIMESTAMP, user=create_admin_user())]
modified_workspace.resourceVersion = 1
modified_workspace.user = create_workspace_owner_user()
modified_workspace.updatedWhen = FAKE_UPDATE_TIMESTAMP
modified_workspace.properties["address_spaces"] = ["192.168.0.1/24", "10.1.4.0/24"]
modified_workspace.etag = etag
update_item_mock.return_value = modified_workspace

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

update_item_mock.assert_called_once_with(modified_workspace, etag)
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_new_address_space", return_value="10.1.4.0/24")
@ patch("api.routes.workspaces.ResourceTemplateRepository.get_template_by_name_and_version")
@ patch("api.dependencies.workspaces.WorkspaceRepository.get_workspace_by_id")
@ patch("api.routes.resource_helpers.send_resource_request_message", return_value=sample_resource_operation(resource_id=SERVICE_ID, operation_id=OPERATION_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_creates_workspace_service_with_address_space_workspace_has_no_address_spaces_property(self, create_workspace_service_item_mock, __, ___, get_workspace_mock, resource_template_repo, _____, app, client, workspace_service_input, basic_workspace_service_template, basic_resource_template):
workspace = sample_workspace()
get_workspace_mock.return_value = workspace
basic_workspace_service_template.properties["address_space"]: str = Field()
create_workspace_service_item_mock.return_value = [sample_workspace_service(), basic_workspace_service_template]
resource_template_repo.return_value = [basic_workspace_service_template, basic_resource_template]
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_400_BAD_REQUEST
assert response.text == strings.WORKSPACE_DOES_NOT_HAVE_ADDRESS_SPACES_PROPERTY

# [POST] /workspaces/{workspace_id}/workspace-services
@ patch("api.dependencies.workspaces.WorkspaceRepository.get_workspace_by_id")
@ patch("api.routes.workspaces.OperationRepository.resource_has_deployed_operation", return_value=True)
Expand Down
8 changes: 8 additions & 0 deletions docs/tre-workspace-authors/authoring-workspace-templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ 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 sapces

Some workspace services may require additional address spaces to be provisioned. This may be as 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.

## User resource bundle manifests

User Resource bundles are generated in the same way as workspace bundles and workspace services bundles.
Expand Down
16 changes: 14 additions & 2 deletions templates/workspaces/base/porter.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: tre-workspace-base
version: 0.7.7
version: 0.8.0
description: "A base Azure TRE workspace"
dockerfile: Dockerfile.tmpl
registry: azuretre
Expand Down Expand Up @@ -154,6 +154,10 @@ install:
enable_local_debugging: "{{ bundle.parameters.enable_local_debugging }}"
register_aad_application: "{{ bundle.parameters.register_aad_application }}"
create_aad_groups: "{{ bundle.parameters.create_aad_groups }}"
arm_tenant_id: "{{ bundle.credentials.azure_tenant_id }}"
arm_client_id: "{{ bundle.credentials.azure_client_id }}"
arm_client_secret: "{{ bundle.credentials.azure_client_secret }}"
arm_use_msi: "{{ bundle.parameters.arm_use_msi }}"
auth_client_id: "{{ bundle.credentials.auth_client_id }}"
auth_client_secret: "{{ bundle.credentials.auth_client_secret }}"
auth_tenant_id: "{{ bundle.credentials.auth_tenant_id }}"
Expand Down Expand Up @@ -183,7 +187,7 @@ install:

upgrade:
- terraform:
description: "Deploy workspace"
description: "Upgrade workspace"
vars:
tre_id: "{{ bundle.parameters.tre_id }}"
tre_resource_id: "{{ bundle.parameters.id }}"
Expand All @@ -193,6 +197,10 @@ upgrade:
enable_local_debugging: "{{ bundle.parameters.enable_local_debugging }}"
register_aad_application: "{{ bundle.parameters.register_aad_application }}"
create_aad_groups: "{{ bundle.parameters.create_aad_groups }}"
arm_tenant_id: "{{ bundle.credentials.azure_tenant_id }}"
arm_client_id: "{{ bundle.credentials.azure_client_id }}"
arm_client_secret: "{{ bundle.credentials.azure_client_secret }}"
arm_use_msi: "{{ bundle.parameters.arm_use_msi }}"
auth_client_id: "{{ bundle.credentials.auth_client_id }}"
auth_client_secret: "{{ bundle.credentials.auth_client_secret }}"
auth_tenant_id: "{{ bundle.credentials.auth_tenant_id }}"
Expand Down Expand Up @@ -249,6 +257,10 @@ uninstall:
enable_local_debugging: "{{ bundle.parameters.enable_local_debugging }}"
register_aad_application: "{{ bundle.parameters.register_aad_application }}"
create_aad_groups: "{{ bundle.parameters.create_aad_groups }}"
arm_tenant_id: "{{ bundle.credentials.azure_tenant_id }}"
arm_client_id: "{{ bundle.credentials.azure_client_id }}"
arm_client_secret: "{{ bundle.credentials.azure_client_secret }}"
arm_use_msi: "{{ bundle.parameters.arm_use_msi }}"
auth_client_id: "{{ bundle.credentials.auth_client_id }}"
auth_client_secret: "{{ bundle.credentials.auth_client_secret }}"
auth_tenant_id: "{{ bundle.credentials.auth_tenant_id }}"
Expand Down
9 changes: 9 additions & 0 deletions templates/workspaces/base/template_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@
"custom"
]
},
"address_spaces": {
"type": "array",
"title": "Address spaces",
"description": "Network address space to be used by the workspace.",
"updateable": true
},
"auth_type": {
"type": "string",
"title": "Workspace Authentication Type",
Expand Down Expand Up @@ -264,6 +270,9 @@
"aad_redirect_uris": {
"classNames": "tre-hidden"
},
"address_spaces": {
"classNames": "tre-hidden"
},
"ui:order": [
"display_name",
"description",
Expand Down
28 changes: 14 additions & 14 deletions templates/workspaces/base/terraform/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 59 additions & 0 deletions templates/workspaces/base/terraform/network/network.tf
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,65 @@ resource "azurerm_subnet_route_table_association" "rt_services_subnet_associatio
]
}

data "azurerm_client_config" "current" {}

resource "null_resource" "az_login_sp" {

count = var.arm_use_msi == true ? 0 : 1
provisioner "local-exec" {
command = "az login --service-principal --username ${var.arm_client_id} --password ${var.arm_client_secret} --tenant ${var.arm_tenant_id}"
}

triggers = {
timestamp = timestamp()
}

}

resource "null_resource" "az_login_msi" {

count = var.arm_use_msi == true ? 1 : 0
provisioner "local-exec" {
command = "az login --identity -u '${data.azurerm_client_config.current.client_id}'"
}

triggers = {
timestamp = timestamp()
}
}

resource "null_resource" "ws_core_peer_sync" {
depends_on = [
azurerm_virtual_network_peering.core_ws_peer,
null_resource.az_login_sp,
null_resource.az_login_msi
]
triggers = {
vnet2addr = join(",", azurerm_virtual_network.ws.address_space)
}
provisioner "local-exec" {
command = <<CMD
az network vnet peering sync --ids ${azurerm_virtual_network_peering.ws_core_peer.id}
CMD
}
}

resource "null_resource" "core_ws_sync" {
depends_on = [
azurerm_virtual_network_peering.core_ws_peer,
null_resource.az_login_sp,
null_resource.az_login_msi
]
triggers = {
vnet2addr = join(",", azurerm_virtual_network.ws.address_space)
}
provisioner "local-exec" {
command = <<CMD
az network vnet peering sync --ids ${azurerm_virtual_network_peering.core_ws_peer.id}
CMD
}
}

resource "azurerm_subnet_route_table_association" "rt_webapps_subnet_association" {
route_table_id = data.azurerm_route_table.rt.id
subnet_id = azurerm_subnet.webapps.id
Expand Down
6 changes: 5 additions & 1 deletion templates/workspaces/base/terraform/network/providers.tf
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = ">= 3.8.0"
version = "=3.33.0"
}
null = {
source = "hashicorp/null"
version = "=3.2.1"
}
}
}
Loading