Skip to content
Snippets Groups Projects
__main__.py 7.91 KiB
Newer Older
# SPDX-FileCopyrightText: Helmholtz-Zentrum Dresden-Rossendorf e.V. (HZDR)
#
# SPDX-License-Identifier: MIT
from typing import List, Union

from dotenv import load_dotenv
from helmholtz_cloud_agent.core.main import HCAApplication
from helmholtz_cloud_agent.messages import (
    ErrorV1,
    ResourceAllocateV1,
    ResourceCreatedV1,
)
from httpx import HTTPError
from pydantic import BaseModel, Field, TypeAdapter, ValidationError, field_validator

from .errors import ValidationExceptionFactory
from .gitlab_api import GitlabApi
from .logging import Logging
from .mattermost_api import MattermostApi
from .resource_spec import MmTeamResourceSpecV1

@app.handle(message_type="ResourceAllocateV1")
def resource_allocate_v1(
    properties, payload: ResourceAllocateV1
) -> Union[ResourceCreatedV1, ErrorV1]:
    """
    Allocates resource, i.e. creates a Mattermost team.

    Args:
        properties:
            Properties of the request.
        payload (ResourceAllocateV1):
            Payload of the request.

    Returns:
        Union[ResourceCreatedV1, ErrorV1]:
            On success, an object with the user ID is returned;
            on failure, an object with the error message is returned.

    Raises:
        ValidationError:
            Errors reported during validation.
    """

    load_dotenv(dotenv_path="./mattermost/.env")
    mm_token: str = os.getenv("MM_TOKEN")
    gl_token: str = os.getenv("GL_TOKEN")
    mm_domain: str = os.getenv("MM_DOMAIN")
    gl_url: str = os.getenv("GL_URL")
    mm_bot_username: str = os.getenv("MM_BOT_USERNAME")

    if (
        mm_token is None
        or gl_token is None
        or gl_url is None
        or mm_bot_username is None
    ):
        raise KeyError("At least one environment variable is missing.")

    gl_api = GitlabApi(logger, gl_url, gl_token)
    mm_api = MattermostApi(logger, mm_domain, mm_token)
    dataclass_validator = TypeAdapter(MmTeamResourceSpecV1)
    try:
        spec = dataclass_validator.validate_python(payload.specification)
    except ValidationError as validation_error:
        validation_exception_factory: ValidationExceptionFactory = (
            ValidationExceptionFactory(validation_error)
        )
        exception_list: List = validation_exception_factory.create_exception_list()
        logger.log_validation_exception(exception_list)
        return ErrorV1(type="Validation Errors", message=str(exception_list))

    team_name = spec.team_name
    team_slug = spec.team_slug
    invite_only = spec.invite_only
    extern_uid = payload.target_entity.user_id_target
    mm_bot_user = mm_api.get_user_by_username(mm_bot_username)
    try:
        requesting_gl_user = gl_api.get_user(extern_uid=extern_uid)
    except IndexError:
        message_account = (
            f"Team '{team_name}' has not been created, because user ({extern_uid}) is not yet known "
            f"in the Helmholtz Codebase or the Helmholtz ID account is not linked to the Helmholtz "
            f"Codebase account."
        logger.log_info_message(
            message=message_account,
            user=extern_uid,
            team_name=team_name,
            team_slug=team_slug,
        )
        return ErrorV1(type="User Unknown Error", message=message_account)
    # note: get user by gitlab id/auth data is not supported by MM API, but email is a unique mapping as well
    username = requesting_gl_user.username
    requesting_mm_user = mm_api.get_user_by_email(requesting_gl_user.email)
    is_team_slug_available = mm_api.is_team_slug_available(team_slug=team_slug)
    if not is_team_slug_available:
        message_slug = (
            f"Team '{team_name}' has not been created, "
            f"because team slug '{team_slug}' has already been taken."
        )
        logger.log_info_message(
            message=message_slug,
            user=username,
            team_name=team_name,
            team_slug=team_slug,
        return ErrorV1(type="Team Slug Error", message=message_slug)
    try:
        team_id = mm_api.create_team(
            team_name=team_name,
            team_slug=team_slug,
        )
        success_team_creation = team_id != ""
        if not success_team_creation:
            message_team = (
                f"Team '{team_name}' has not been created and team ID has not been set."
            )
            logger.log_info_message(
                message=message_team,
                user=username,
                team_name=team_name,
                team_slug=team_slug,
            return ErrorV1(type="Team ID Error", message=message_team)
        logger.log_info_message(
            message=f"Created team (ID='{team_id}') with name '{team_name}' and slug '{team_slug}' "
            f"that is invite_only={str(invite_only)}.",
            user=username,
            team_name=team_name,
            team_slug=team_slug,
        )
        team_type = mm_api.update_team_privacy(team_id=team_id, invite_only=invite_only)
        success_team_privacy = team_type != ""
        if not success_team_privacy:
            message_privacy = f"Privacy of team '{team_name}' could not be set to invite_only={str(invite_only)}."
            logger.log_info_message(
                message=message_privacy,
                user=username,
                team_name=team_name,
                team_slug=team_slug,
            return ErrorV1(type="Team Privacy Error", message=message_privacy)
        logger.log_info_message(
            message=f"Set privacy (invite_only={str(invite_only)}) of team (ID='{team_id}') "
            f"to type={team_type}.",
            user=username,
            team_name=team_name,
            team_slug=team_slug,
        )
        success_status_add = mm_api.add_user_to_team(
            user_id=requesting_mm_user["id"], as_team_admin=True, team_id=team_id
        )
        success_status_remove = True
        if success_status_add and requesting_mm_user["id"] != mm_bot_user["id"]:
            success_status_remove = mm_api.remove_user_from_team(
                user_id=mm_bot_user["id"], team_id=team_id
            )
        if success_status_add and success_status_remove:
            # success
            return ResourceCreatedV1(id=team_id)
        elif success_status_add and not success_status_remove:
            # partially successful
            message_bot = f"Team creation of team '{team_name}' succeeded, but removal of bot from team failed."
            logger.log_info_message(
                message=message_bot,
                user=username,
                team_name=team_name,
                team_slug=team_slug,
            return ErrorV1(type="Bot Removal Error", message=message_bot)
            message_failed = f"Creation of requested team '{team_name}' failed."
            logger.log_info_message(
                message=message_failed,
                user=username,
                team_name=team_name,
                team_slug=team_slug,
            return ErrorV1(type="Team Creation Error", message=message_failed)
    except HTTPError as http_error:
        message_http = (
            f"Unexpected HTTPError: status={str(http_error.response.status_code)}, "
            f"msg={http_error.response.reason_phrase}"
        logger.log_info_message(
            message=message_http,
            user=username,
            team_name=team_name,
            team_slug=team_slug,
        # catch all HTTP errors: 400 <= status <= 599
        return ErrorV1(type="HTTP Error", message=message_http)
    except Exception as err:
        message_error = f"Unexpected {err=}, {type(err)=}"
        logger.log_info_message(
            message=message_error,
            user=username,
            team_name=team_name,
            team_slug=team_slug,
        return ErrorV1(type="Error", message=message_error)