Skip to content

Commit

Permalink
Merge branch 'main' into marrobi/issue3810
Browse files Browse the repository at this point in the history
  • Loading branch information
marrobi authored Feb 2, 2024
2 parents 3e772dc + 3196089 commit dc3d0d9
Show file tree
Hide file tree
Showing 148 changed files with 2,047 additions and 1,874 deletions.
11 changes: 8 additions & 3 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,10 @@
"console": "integratedTerminal",
"preLaunchTask": "Copy_env_file_for_api_debug",
"cwd": "${workspaceFolder}/api_app",
"envFile": "${workspaceFolder}/api_app/.env"
"envFile": "${workspaceFolder}/api_app/.env",
"env": {
"OTEL_RESOURCE_ATTRIBUTES": "service.name=api,service.instance.id=local_debug,service.version=dev"
}
},
{
"name": "E2E Extended",
Expand Down Expand Up @@ -190,8 +193,10 @@
"cwd": "${workspaceFolder}/resource_processor",
"envFile": "${workspaceFolder}/core/private.env",
"env": {
"PYTHONPATH": "."
}
"PYTHONPATH": ".",
"OTEL_RESOURCE_ATTRIBUTES": "service.name=resource_processor,service.instance.id=local_debug,service.version=dev"
},
"justMyCode": false
},
{
"name": "Debug Python file",
Expand Down
1 change: 1 addition & 0 deletions .github/linters/.hadolint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ ignored:
# sometimes pinned versions are removed from the package source so we decided to ignore this rule.
- DL3008
- DL3018
- DL3029
58 changes: 55 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,73 @@
<!-- markdownlint-disable MD041 -->
## 0.16.0 (Unreleased)
## 0.17.0 (Unreleased)

**BREAKING CHANGES & MIGRATIONS**:
To resolve the Airlock import issue described in ([#3767](https://github.com/microsoft/AzureTRE/pull/3767)), the new airlock import review tempalte will need to be registered using `make workspace_bundle BUNDLE=airlock-import-review`. Any existing airlock import review workspaces will need to be upgraded. After upgrading, run `make deploy-core` to reinstate any deleted DNS records.

FEATURES:

ENHANCEMENTS:
* Switch from OpenCensus to OpenTelemetry for logging ([#3762](https://github.com/microsoft/AzureTRE/pull/3762))
* Extend PowerShell auto start script to start core VMs ([#3811](https://github.com/microsoft/AzureTRE/issues/3811))
* Use managed identity for API connection to CosmosDB ([#345](https://github.com/microsoft/AzureTRE/issues/345))
* Switch to Structured Firewall Logs ([#3816](https://github.com/microsoft/AzureTRE/pull/3816))
* Support for building core and workspace service bundles on arm64 platforms ([#3823](https://github.com/microsoft/AzureTRE/issues/3823))

BUG FIXES:
* Fix issue with workspace menu not working correctly([#3819](https://github.com/microsoft/AzureTRE/issues/3819))
* Fix issue with connect button showing when no uri([#3820](https://github.com/microsoft/AzureTRE/issues/3820))
* Fix user resource upgrade validation: use the parent_service_template_name instead of the parent_resource_id. ([#3824](https://github.com/microsoft/AzureTRE/issues/3824))

COMPONENTS:

## 0.16.0 (December 1, 2023)

**BREAKING CHANGES & MIGRATIONS**:
To resolve the Airlock import issue described in ([#3767](https://github.com/microsoft/AzureTRE/pull/3767)), the new airlock import review template will need to be registered using `make workspace_bundle BUNDLE=airlock-import-review`. Any existing airlock import review workspaces will need to be upgraded.

Once you have upgraded the import review workspaces, delete the private endpoint, named `pe-stg-import-inprogress-blob-*` in the core resource group, and then run `make deploy-core` to reinstate the private endpoint and DNS records.

ENHANCEMENTS:
* Security updates aligning to Dependabot, MS Defender for Cloud and Synk ([#3796](https://github.com/microsoft/AzureTRE/issues/3796))

BUG FIXES:
* Fix issue where updates fail as read only is not configured consistently on schema fields ([#3691](https://github.com/microsoft/AzureTRE/issues/3691))
* When geting avaialble address spaces allow those allocated to deleted workspaces to be reassigned ([#3691](https://github.com/microsoft/AzureTRE/issues/3691))
* When getting available address spaces allow those allocated to deleted workspaces to be reassigned ([#3691](https://github.com/microsoft/AzureTRE/issues/3691))
* Update Python packages, and fix breaking changes ([#3764](https://github.com/microsoft/AzureTRE/issues/3764))
* Enabling support for more than 20 users/groups in Workspace API ([#3759](https://github.com/microsoft/AzureTRE/pull/3759 ))
* Airlock Import Review workspace uses dedicated DNS zone to prevent conflict with core ([#3767](https://github.com/microsoft/AzureTRE/pull/3767))

COMPONENTS:
| name | version |
| ----- | ----- |
| devops | 0.5.1 |
| core | 0.9.0 |
| ui | 0.5.17 |
| tre-workspace-base | 1.5.3 |
| tre-workspace-unrestricted | 0.11.4 |
| tre-workspace-airlock-import-review | 0.12.16 |
| tre-service-mlflow | 0.7.7 |
| tre-workspace-service-health | 0.2.5 |
| tre-service-databricks | 1.0.3 |
| tre-service-innereye | 0.6.4 |
| tre-workspace-service-gitea | 0.8.7 |
| tre-workspace-service-mysql | 0.4.5 |
| tre-workspace-service-ohdsi | 0.2.4 |
| tre-service-guacamole-linuxvm | 0.6.9 |
| tre-service-guacamole-export-reviewvm | 0.1.8 |
| tre-service-guacamole-windowsvm | 0.7.9 |
| tre-service-guacamole-import-reviewvm | 0.2.8 |
| tre-service-guacamole | 0.10.6 |
| tre-user-resource-aml-compute-instance | 0.5.7 |
| tre-service-azureml | 0.8.10 |
| tre-shared-service-cyclecloud | 0.5.5 |
| tre-shared-service-databricks-private-auth | 0.1.5 |
| tre-shared-service-gitea | 0.6.10 |
| tre-shared-service-airlock-notifier | 0.9.0 |
| tre-shared-service-admin-vm | 0.4.3 |
| tre-shared-service-certs | 0.5.1 |
| tre-shared-service-sonatype-nexus | 2.8.13 |
| tre-shared-service-firewall | 1.1.5 |


## 0.15.2 (October 24, 2023)

Expand Down
3 changes: 2 additions & 1 deletion api_app/.env.sample
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# API configuration
# -----------------
# When debug is set to True, debugging information for unhandled exceptions is shown in the swagger UI and logging is more verbose
# DEBUG=True
# LOGGING_LEVEL can be set to DEBUG, INFO, WARNING, ERROR or CRITICAL
LOGGING_LEVEL="INFO"

# OAUTH information - client ids etc. for the AAD Apps
# ----------------------------------------------------
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.16.9"
__version__ = "0.18.2"
2 changes: 1 addition & 1 deletion api_app/api/dependencies/airlock.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from fastapi import Depends, HTTPException, Path, status
from pydantic import UUID4

from api.dependencies.database import get_repository
from api.helpers import get_repository
from db.repositories.airlock_requests import AirlockRequestRepository
from models.domain.airlock_request import AirlockRequest
from db.errors import EntityDoesNotExist, UnableToAccessDatabase
Expand Down
138 changes: 72 additions & 66 deletions api_app/api/dependencies/database.py
Original file line number Diff line number Diff line change
@@ -1,80 +1,86 @@
import logging
from typing import Callable, Type

from azure.cosmos.aio import CosmosClient
from azure.cosmos.aio import CosmosClient, DatabaseProxy, ContainerProxy
from azure.mgmt.cosmosdb.aio import CosmosDBManagementClient
from fastapi import Depends, FastAPI, HTTPException
from fastapi import Request, status
from core import config, credentials
from db.errors import UnableToAccessDatabase
from db.repositories.base import BaseRepository
from resources import strings

from core.config import MANAGED_IDENTITY_CLIENT_ID, STATE_STORE_ENDPOINT, STATE_STORE_KEY, STATE_STORE_SSL_VERIFY, SUBSCRIPTION_ID, RESOURCE_MANAGER_ENDPOINT, CREDENTIAL_SCOPES, RESOURCE_GROUP_NAME, COSMOSDB_ACCOUNT_NAME, STATE_STORE_DATABASE
from core.credentials import get_credential_async
from services.logging import logger


class Singleton(type):
_instances = {}

def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]

async def connect_to_db() -> CosmosClient:
logging.debug(f"Connecting to {config.STATE_STORE_ENDPOINT}")

try:
async with credentials.get_credential_async() as credential:
primary_master_key = await get_store_key(credential)
class Database(metaclass=Singleton):

if config.STATE_STORE_SSL_VERIFY:
_cosmos_client: CosmosClient = None
_database_proxy: DatabaseProxy = None

def __init__(cls):
pass

@classmethod
async def _connect_to_db(cls) -> CosmosClient:
logger.debug(f"Connecting to {STATE_STORE_ENDPOINT}")

credential = await get_credential_async()
if MANAGED_IDENTITY_CLIENT_ID:
logger.debug("Connecting with managed identity")
cosmos_client = CosmosClient(
url=config.STATE_STORE_ENDPOINT, credential=primary_master_key
url=STATE_STORE_ENDPOINT,
credential=credential
)
else:
# ignore TLS (setup is a pain) when using local Cosmos emulator.
cosmos_client = CosmosClient(
config.STATE_STORE_ENDPOINT, primary_master_key, connection_verify=False
)
logging.debug("Connection established")
return cosmos_client
except Exception:
logging.exception("Connection to state store could not be established.")


async def get_store_key(credential) -> str:
if config.STATE_STORE_KEY:
primary_master_key = config.STATE_STORE_KEY
else:
async with CosmosDBManagementClient(
credential,
subscription_id=config.SUBSCRIPTION_ID,
base_url=config.RESOURCE_MANAGER_ENDPOINT,
credential_scopes=config.CREDENTIAL_SCOPES
) as cosmosdb_mng_client:
database_keys = await cosmosdb_mng_client.database_accounts.list_keys(
resource_group_name=config.RESOURCE_GROUP_NAME,
account_name=config.COSMOSDB_ACCOUNT_NAME,
)
primary_master_key = database_keys.primary_master_key

return primary_master_key
logger.debug("Connecting with key")
primary_master_key = await cls._get_store_key(credential)

if STATE_STORE_SSL_VERIFY:
logger.debug("Connecting with SSL verification")
cosmos_client = CosmosClient(
url=STATE_STORE_ENDPOINT,
credential=primary_master_key
)
else:
logger.debug("Connecting without SSL verification")
# ignore TLS (setup is a pain) when using local Cosmos emulator.
cosmos_client = CosmosClient(
url=STATE_STORE_ENDPOINT,
credential=primary_master_key,
connection_verify=False
)
logger.debug("Connection established")
return cosmos_client

async def get_db_client(app: FastAPI) -> CosmosClient:
if not app.state.cosmos_client:
app.state.cosmos_client = await connect_to_db()
return app.state.cosmos_client

@classmethod
async def _get_store_key(cls, credential) -> str:
logger.debug("Getting store key")
if STATE_STORE_KEY:
primary_master_key = STATE_STORE_KEY
else:
async with CosmosDBManagementClient(
credential,
subscription_id=SUBSCRIPTION_ID,
base_url=RESOURCE_MANAGER_ENDPOINT,
credential_scopes=CREDENTIAL_SCOPES
) as cosmosdb_mng_client:
database_keys = await cosmosdb_mng_client.database_accounts.list_keys(
resource_group_name=RESOURCE_GROUP_NAME,
account_name=COSMOSDB_ACCOUNT_NAME,
)
primary_master_key = database_keys.primary_master_key

async def get_db_client_from_request(request: Request) -> CosmosClient:
return await get_db_client(request.app)
return primary_master_key

@classmethod
async def get_container_proxy(cls, container_name) -> ContainerProxy:
if cls._cosmos_client is None:
cls._cosmos_client = await cls._connect_to_db()

def get_repository(
repo_type: Type[BaseRepository],
) -> Callable[[CosmosClient], BaseRepository]:
async def _get_repo(
client: CosmosClient = Depends(get_db_client_from_request),
) -> BaseRepository:
try:
return await repo_type.create(client)
except UnableToAccessDatabase:
logging.exception(strings.STATE_STORE_ENDPOINT_NOT_RESPONDING)
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail=strings.STATE_STORE_ENDPOINT_NOT_RESPONDING,
)
if cls._database_proxy is None:
cls._database_proxy = cls._cosmos_client.get_database_client(STATE_STORE_DATABASE)

return _get_repo
return cls._database_proxy.get_container_client(container_name)
2 changes: 1 addition & 1 deletion api_app/api/dependencies/shared_services.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from fastapi import Depends, HTTPException, Path, status
from pydantic import UUID4

from api.dependencies.database import get_repository
from api.helpers import get_repository
from db.errors import EntityDoesNotExist
from resources import strings
from models.domain.shared_service import SharedService
Expand Down
2 changes: 1 addition & 1 deletion api_app/api/dependencies/workspace_service_templates.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from fastapi import Depends, HTTPException, Path, status

from api.dependencies.database import get_repository
from api.helpers import get_repository
from db.errors import EntityDoesNotExist
from db.repositories.resource_templates import ResourceTemplateRepository
from models.domain.resource import ResourceType
Expand Down
2 changes: 1 addition & 1 deletion api_app/api/dependencies/workspaces.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from fastapi import Depends, HTTPException, Path, status
from pydantic import UUID4

from api.dependencies.database import get_repository
from api.helpers import get_repository
from db.errors import EntityDoesNotExist, ResourceIsNotDeployed
from db.repositories.operations import OperationRepository
from db.repositories.user_resources import UserResourceRepository
Expand Down
9 changes: 5 additions & 4 deletions api_app/api/errors/generic_error.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import logging
from resources import strings

from fastapi import Request
from fastapi.responses import PlainTextResponse

from services.logging import logger


async def generic_error_handler(_: Request, exception: Exception) -> PlainTextResponse:
logging.debug("=====================================")
logging.exception(exception)
logging.debug("=====================================")
logger.debug("=====================================")
logger.exception(exception)
logger.debug("=====================================")
return PlainTextResponse(strings.UNABLE_TO_PROCESS_REQUEST, status_code=500)
22 changes: 22 additions & 0 deletions api_app/api/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from typing import Callable, Type

from fastapi import HTTPException, status

from db.errors import UnableToAccessDatabase
from db.repositories.base import BaseRepository
from resources.strings import UNABLE_TO_GET_STATE_STORE_CLIENT
from services.logging import logger


def get_repository(repo_type: Type[BaseRepository],) -> Callable:
async def _get_repo() -> BaseRepository:
try:
return await repo_type.create()
except UnableToAccessDatabase:
logger.exception(UNABLE_TO_GET_STATE_STORE_CLIENT)
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail=UNABLE_TO_GET_STATE_STORE_CLIENT,
)

return _get_repo
Loading

0 comments on commit dc3d0d9

Please sign in to comment.