Skip to content

Commit

Permalink
Merge branch 'main' into marrobi/issue2451
Browse files Browse the repository at this point in the history
  • Loading branch information
tamirkamara authored Aug 14, 2022
2 parents 56e9f0b + db86454 commit c2c1902
Show file tree
Hide file tree
Showing 43 changed files with 453 additions and 242 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ FEATURES:

ENHANCEMENTS:

*
* 'CreationTime' field was added to Airlock requests ([#2432](https://github.com/microsoft/AzureTRE/issues/2432))
* Bundles mirror Terraform plugins when built ([#2446](https://github.com/microsoft/AzureTRE/pull/2446))

BUG FIXES:

* Azure monitor resourced provided by Terraform and don't allow ingestion over internet ([#2375](https://github.com/microsoft/AzureTRE/pull/2375)).
* Azure monitor resourced provided by Terraform and don't allow ingestion over internet ([#2375](https://github.com/microsoft/AzureTRE/pull/2375))
* Enable route table on the Airlock Processor subnet ([#2414](https://github.com/microsoft/AzureTRE/pull/2414))
* Support for _Standard_ app service plan SKUs ([#2415](https://github.com/microsoft/AzureTRE/pull/2415))
* Fix Azure ML Workspace deletion ([#2452](https://github.com/microsoft/AzureTRE/pull/2452))
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.4.8"
__version__ = "0.4.10"
11 changes: 8 additions & 3 deletions api_app/api/routes/workspaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,14 @@ async def retrieve_users_active_workspaces(request: Request, user=Depends(get_cu
return WorkspacesInList(workspaces=user_workspaces)


@workspaces_shared_router.get("/workspaces/{workspace_id}", response_model=WorkspaceInResponse, name=strings.API_GET_WORKSPACE_BY_ID, dependencies=[Depends(get_current_workspace_owner_or_researcher_user_or_tre_admin)])
async def retrieve_workspace_by_workspace_id(workspace=Depends(get_workspace_by_id_from_path)) -> WorkspaceInResponse:
return WorkspaceInResponse(workspace=workspace)
@workspaces_core_router.get("/workspaces/{workspace_id}", response_model=WorkspaceInResponse, name=strings.API_GET_WORKSPACE_BY_ID)
async def retrieve_workspace_by_workspace_id(user=Depends(get_current_tre_user_or_tre_admin), workspace=Depends(get_workspace_by_id_from_path)) -> WorkspaceInResponse:
access_service = get_access_service()
user_role_assignments = get_user_role_assignments(user)
if access_service.get_workspace_role(user, workspace, user_role_assignments) != WorkspaceRole.NoRole:
return WorkspaceInResponse(workspace=workspace)
else:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=strings.ACCESS_USER_IS_NOT_OWNER_OR_RESEARCHER)


@workspaces_core_router.post("/workspaces", status_code=status.HTTP_202_ACCEPTED, response_model=OperationInResponse, name=strings.API_CREATE_WORKSPACE, dependencies=[Depends(get_current_admin_user)])
Expand Down
2 changes: 2 additions & 0 deletions api_app/db/repositories/airlock_requests.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import copy
import uuid

from datetime import datetime
from typing import List
from pydantic import UUID4
from azure.cosmos.exceptions import CosmosResourceNotFoundError
Expand Down Expand Up @@ -67,6 +68,7 @@ def create_airlock_request_item(self, airlock_request_input: AirlockRequestInCre
workspaceId=workspace_id,
businessJustification=airlock_request_input.businessJustification,
requestType=airlock_request_input.requestType,
creationTime=datetime.utcnow().timestamp(),
properties=resource_spec_parameters
)

Expand Down
1 change: 1 addition & 0 deletions api_app/models/domain/airlock_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ class AirlockRequest(AirlockResource):
files: List[str] = Field([], title="Files of the request")
businessJustification: str = Field("Business Justifications", title="Explanation that will be provided to the request reviewer")
status = AirlockRequestStatus.Draft
creationTime: float = Field(None, title="Creation time of the request")
errorMessage: Optional[str] = Field(title="Present only if the request have failed, provides the reason of the failure.")
2 changes: 2 additions & 0 deletions api_app/models/schemas/airlock_request.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from datetime import datetime
from typing import List
from pydantic import BaseModel, Field
from models.domain.airlock_resource import AirlockResourceType
Expand All @@ -12,6 +13,7 @@ def get_sample_airlock_request(workspace_id: str, airlock_request_id: str) -> di
"requestType": "import",
"files": [],
"businessJustification": "some business justification",
"creationTime": datetime.utcnow().timestamp(),
"resourceType": AirlockResourceType.AirlockRequest
}

Expand Down
61 changes: 29 additions & 32 deletions api_app/tests_ma/test_api/test_routes/test_workspaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,35 @@ async def test_get_workspaces_returns_correct_data_when_resources_exist(self, ac
assert workspaces_from_response[0]["id"] == valid_ws_1.id
assert workspaces_from_response[1]["id"] == valid_ws_2.id

# [GET] /workspaces/{workspace_id}
@patch("api.dependencies.workspaces.WorkspaceRepository.get_workspace_by_id")
@patch("api.routes.workspaces.get_user_role_assignments")
async def test_get_workspace_by_id_get_returns_workspace_if_found(self, access_service_mock, get_workspace_mock, app, client):
auth_info_user_in_workspace_owner_role = {'sp_id': 'ab123', 'app_role_id_workspace_owner': 'ab124', 'app_role_id_workspace_researcher': 'ab125', 'app_role_id_workspace_airlock_manager': 'ab130'}
workspace = sample_workspace(auth_info=auth_info_user_in_workspace_owner_role)
get_workspace_mock.return_value = sample_workspace(auth_info=auth_info_user_in_workspace_owner_role)
access_service_mock.return_value = [RoleAssignment('ab123', 'ab124')]

response = await client.get(app.url_path_for(strings.API_GET_WORKSPACE_BY_ID, workspace_id=WORKSPACE_ID))
actual_resource = response.json()["workspace"]
assert actual_resource["id"] == workspace.id

# [GET] /workspaces/{workspace_id}
@patch("api.dependencies.workspaces.WorkspaceRepository.get_workspace_by_id", side_effect=EntityDoesNotExist)
@patch("api.routes.workspaces.get_user_role_assignments")
async def test_get_workspace_by_id_get_returns_404_if_resource_is_not_found(self, access_service_mock, _, app, client):
access_service_mock.return_value = [RoleAssignment('ab123', 'ab124')]
response = await client.get(app.url_path_for(strings.API_GET_WORKSPACE_BY_ID, workspace_id=WORKSPACE_ID))
assert response.status_code == status.HTTP_404_NOT_FOUND

# [GET] /workspaces/{workspace_id}
@patch("api.dependencies.workspaces.WorkspaceRepository.get_workspace_by_id")
@patch("api.routes.workspaces.get_user_role_assignments")
async def test_get_workspace_by_id_get_returns_422_if_workspace_id_is_not_a_uuid(self, access_service_mock, _, app, client):
access_service_mock.return_value = [RoleAssignment('ab123', 'ab124')]
response = await client.get(app.url_path_for(strings.API_GET_WORKSPACE_BY_ID, workspace_id="not_valid"))
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY


class TestWorkspaceRoutesThatRequireAdminRights:
@pytest.fixture(autouse=True, scope='class')
Expand Down Expand Up @@ -280,16 +309,6 @@ async def test_get_workspaces_returns_correct_data_when_resources_exist(self, ge
assert workspaces_from_response[1]["id"] == valid_ws_2.id
assert workspaces_from_response[2]["id"] == valid_ws_3.id

# [GET] /workspaces/{workspace_id}
@patch("api.dependencies.workspaces.WorkspaceRepository.get_workspace_by_id")
async def test_get_workspace_by_id_get_returns_workspace_if_found(self, get_workspace_mock, app, client):
workspace = sample_workspace()
get_workspace_mock.return_value = sample_workspace()

response = await client.get(app.url_path_for(strings.API_GET_WORKSPACE_BY_ID, workspace_id=WORKSPACE_ID))
actual_resource = response.json()["workspace"]
assert actual_resource["id"] == workspace.id

# [POST] /workspaces/
@ patch("api.routes.workspaces.ResourceTemplateRepository.get_template_by_name_and_version")
@ patch("api.routes.resource_helpers.send_resource_request_message", return_value=sample_resource_operation(resource_id=WORKSPACE_ID, operation_id=OPERATION_ID))
Expand Down Expand Up @@ -698,28 +717,6 @@ def log_in_with_researcher_user(self, app, researcher_user):
yield
app.dependency_overrides = {}

# [GET] /workspaces/{workspace_id}
@patch("api.dependencies.workspaces.WorkspaceRepository.get_workspace_by_id", side_effect=EntityDoesNotExist)
async def test_get_workspace_by_id_get_returns_404_if_resource_is_not_found(self, _, app, client):
response = await client.get(app.url_path_for(strings.API_GET_WORKSPACE_BY_ID, workspace_id=WORKSPACE_ID))
assert response.status_code == status.HTTP_404_NOT_FOUND

# [GET] /workspaces/{workspace_id}
@patch("api.dependencies.workspaces.WorkspaceRepository.get_workspace_by_id")
async def test_get_workspace_by_id_get_returns_422_if_workspace_id_is_not_a_uuid(self, _, app, client):
response = await client.get(app.url_path_for(strings.API_GET_WORKSPACE_BY_ID, workspace_id="not_valid"))
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY

# [GET] /workspaces/{workspace_id}
@patch("api.dependencies.workspaces.WorkspaceRepository.get_workspace_by_id")
async def test_get_workspace_by_id_get_returns_workspace_if_found(self, get_workspace_mock, app, client):
workspace = sample_workspace()
get_workspace_mock.return_value = sample_workspace()

response = await client.get(app.url_path_for(strings.API_GET_WORKSPACE_BY_ID, workspace_id=WORKSPACE_ID))
actual_resource = response.json()["workspace"]
assert actual_resource["id"] == workspace.id

# [GET] /workspaces/{workspace_id}/workspace-services
@patch("api.dependencies.workspaces.WorkspaceRepository.get_workspace_by_id", return_value=sample_workspace())
@patch("api.routes.workspaces.WorkspaceServiceRepository.get_active_workspace_services_for_workspace",
Expand Down
3 changes: 3 additions & 0 deletions e2e_tests/resources/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
GITEA_SHARED_SERVICE = "tre-shared-service-gitea"
NEXUS_SHARED_SERVICE = "tre-shared-service-nexus"

GUACAMOLE_WINDOWS_USER_RESOURCE = "tre-service-guacamole-windowsvm"
GUACAMOLE_LINUX_USER_RESOURCE = "tre-service-guacamole-linuxvm"

TEST_WORKSPACE_SERVICE_TEMPLATE = "e2e-test-workspace-service"

# Resource Status
Expand Down
2 changes: 1 addition & 1 deletion e2e_tests/test_airlock.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ async def test_airlock_import_flow(admin_token, verify) -> None:
# 1. create workspace
LOGGER.info("Creating workspace")
payload = {
"templateName": "tre-workspace-base",
"templateName": resource_strings.BASE_WORKSPACE,
"properties": {
"display_name": "E2E test airlock flow",
"description": "workspace for E2E airlock flow",
Expand Down
6 changes: 3 additions & 3 deletions e2e_tests/test_performance.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ async def test_parallel_resource_creations(admin_token, verify) -> None:

for i in range(number_workspaces):
payload = {
"templateName": "tre-workspace-base",
"templateName": strings.BASE_WORKSPACE,
"properties": {
"display_name": f'Perf Test Workspace {i}',
"description": "workspace for perf test",
Expand Down Expand Up @@ -59,7 +59,7 @@ async def test_bulk_updates_to_ensure_each_resource_updated_in_series(admin_toke
if workspace_id == "":
# create the workspace to use
payload = {
"templateName": "tre-workspace-base",
"templateName": strings.BASE_WORKSPACE,
"properties": {
"display_name": "E2E test guacamole service",
"description": "",
Expand All @@ -78,7 +78,7 @@ async def test_bulk_updates_to_ensure_each_resource_updated_in_series(admin_toke
if config.PERF_TEST_WORKSPACE_SERVICE_ID == "":
# create a guac service
service_payload = {
"templateName": "tre-service-guacamole",
"templateName": strings.GUACAMOLE_SERVICE,
"properties": {
"display_name": "Workspace service test",
"description": ""
Expand Down
12 changes: 6 additions & 6 deletions e2e_tests/test_workspace_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
async def test_create_guacamole_service_into_base_workspace(admin_token, verify) -> None:

payload = {
"templateName": "tre-workspace-base",
"templateName": strings.BASE_WORKSPACE,
"properties": {
"display_name": "E2E test guacamole service",
"description": "workspace for E2E",
Expand All @@ -31,7 +31,7 @@ async def test_create_guacamole_service_into_base_workspace(admin_token, verify)
workspace_owner_token, scope_uri = await get_workspace_auth_details(admin_token=admin_token, workspace_id=workspace_id, verify=verify)

service_payload = {
"templateName": "tre-service-guacamole",
"templateName": strings.GUACAMOLE_SERVICE,
"properties": {
"display_name": "Workspace service test",
"description": "Workspace service for E2E test"
Expand All @@ -53,7 +53,7 @@ async def test_create_guacamole_service_into_base_workspace(admin_token, verify)
await post_resource(patch_payload, f'/api{workspace_service_path}', workspace_owner_token, verify, method="PATCH")

user_resource_payload = {
"templateName": "tre-service-guacamole-windowsvm",
"templateName": strings.GUACAMOLE_WINDOWS_USER_RESOURCE,
"properties": {
"display_name": "My VM",
"description": "Will be using this VM for my research",
Expand All @@ -76,7 +76,7 @@ async def test_create_guacamole_service_into_aad_workspace(admin_token, verify)
"""This test will create a Guacamole service but will create a workspace and automatically register the AAD Application"""

payload = {
"templateName": "tre-workspace-base",
"templateName": strings.BASE_WORKSPACE,
"properties": {
"display_name": "E2E test guacamole service",
"description": "workspace for E2E AAD",
Expand All @@ -91,7 +91,7 @@ async def test_create_guacamole_service_into_aad_workspace(admin_token, verify)
workspace_owner_token, scope_uri = await get_workspace_auth_details(admin_token=admin_token, workspace_id=workspace_id, verify=verify)

service_payload = {
"templateName": "tre-service-guacamole",
"templateName": strings.GUACAMOLE_SERVICE,
"properties": {
"display_name": "Workspace service test",
"description": "Workspace service for E2E test"
Expand All @@ -113,7 +113,7 @@ async def test_create_guacamole_service_into_aad_workspace(admin_token, verify)
await post_resource(patch_payload, f'/api{workspace_service_path}', workspace_owner_token, verify, method="PATCH")

user_resource_payload = {
"templateName": "tre-service-guacamole-linuxvm",
"templateName": strings.GUACAMOLE_LINUX_USER_RESOURCE,
"properties": {
"display_name": "Linux VM",
"description": "Extended Tests for Linux",
Expand Down
4 changes: 2 additions & 2 deletions templates/shared_services/firewall/.dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Put files here that you don't want copied into your bundle's invocation image
.gitignore
**/.terraform/*
**/.terraform.lock.hcl
**/*_backend.tf
Dockerfile.tmpl

terraform/import_state.sh
terraform/remove_state.sh
25 changes: 25 additions & 0 deletions templates/shared_services/firewall/Dockerfile.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
FROM debian:buster-slim

ARG BUNDLE_DIR

# This is a template Dockerfile for the bundle's invocation image
# You can customize it to use different base images, install tools and copy configuration files.
#
# Porter will use it as a template and append lines to it for the mixins
# and to set the CMD appropriately for the CNAB specification.
#
# Add the following line to porter.yaml to instruct Porter to use this template
# dockerfile: Dockerfile.tmpl

# You can control where the mixin's Dockerfile lines are inserted into this file by moving "# PORTER_MIXINS" line
# another location in this file. If you remove that line, the mixins generated content is appended to this file.
# PORTER_MIXINS

# Use the BUNDLE_DIR build argument to copy files into the bundle
COPY . $BUNDLE_DIR

# Mirror plugins to prevet network access at runtime
# Remove when available from https://github.com/getporter/terraform-mixin/issues/90
WORKDIR $BUNDLE_DIR/terraform
RUN terraform init -backend=false \
&& terraform providers mirror /usr/local/share/terraform/plugins
6 changes: 3 additions & 3 deletions templates/shared_services/firewall/porter.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
---
name: tre-shared-service-firewall
version: 0.4.1
version: 0.4.2
description: "An Azure TRE Firewall shared service"
dockerfile: Dockerfile.tmpl
registry: azuretre

credentials:
Expand Down Expand Up @@ -50,8 +51,7 @@ parameters:

mixins:
- terraform:
initFile: providers.tf
clientVersion: 1.2.5
clientVersion: 1.2.6

install:
- terraform:
Expand Down
28 changes: 14 additions & 14 deletions templates/shared_services/firewall/terraform/.terraform.lock.hcl

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

Loading

0 comments on commit c2c1902

Please sign in to comment.