Ejemplo n.º 1
0
    async def create_service(
        self,
        new_service: ServiceMetaDataAtDB,
        new_service_access_rights: List[ServiceAccessRightsAtDB],
    ) -> ServiceMetaDataAtDB:

        for access_rights in new_service_access_rights:
            if (
                access_rights.key != new_service.key
                or access_rights.version != new_service.version
            ):
                raise ValueError(
                    f"{access_rights} does not correspond to service {new_service.key}:{new_service.version}"
                )

        async with self.db_engine.acquire() as conn:
            # NOTE: this ensure proper rollback in case of issue
            async with conn.begin() as _transaction:
                row: RowProxy = await (
                    await conn.execute(
                        # pylint: disable=no-value-for-parameter
                        services_meta_data.insert()
                        .values(**new_service.dict(by_alias=True))
                        .returning(literal_column("*"))
                    )
                ).first()
                created_service = ServiceMetaDataAtDB(**row)

                for access_rights in new_service_access_rights:
                    insert_stmt = pg_insert(services_access_rights).values(
                        **access_rights.dict(by_alias=True)
                    )
                    await conn.execute(insert_stmt)
        return created_service
Ejemplo n.º 2
0
def _prepare_service_details(
    service_in_registry: Dict[str, Any],
    service_in_db: ServiceMetaDataAtDB,
    service_access_rights_in_db: List[ServiceAccessRightsAtDB],
    service_owner: Optional[str],
) -> Optional[ServiceOut]:
    # compose service from registry and DB
    composed_service = service_in_registry
    composed_service.update(
        service_in_db.dict(exclude_unset=True, exclude={"owner"}),
        access_rights={rights.gid: rights for rights in service_access_rights_in_db},
        owner=service_owner if service_owner else None,
    )

    # validate the service
    validated_service = None
    try:
        validated_service = ServiceOut(**composed_service)
    except ValidationError as exc:
        logger.warning(
            "could not validate service [%s:%s]: %s",
            composed_service.get("key"),
            composed_service.get("version"),
            exc,
        )
    return validated_service
Ejemplo n.º 3
0
 async def update_service(
     self, patched_service: ServiceMetaDataAtDB
 ) -> ServiceMetaDataAtDB:
     # update the services_meta_data table
     async with self.db_engine.acquire() as conn:
         row: RowProxy = await (
             await conn.execute(
                 # pylint: disable=no-value-for-parameter
                 services_meta_data.update()
                 .where(
                     (services_meta_data.c.key == patched_service.key)
                     & (services_meta_data.c.version == patched_service.version)
                 )
                 .values(**patched_service.dict(by_alias=True, exclude_unset=True))
                 .returning(literal_column("*"))
             )
         ).first()
     updated_service = ServiceMetaDataAtDB(**row)
     return updated_service
Ejemplo n.º 4
0
async def _create_services_in_db(
    app: FastAPI,
    connection: SAConnection,
    service_keys: Set[Tuple[ServiceKey, ServiceVersion]],
    services: Dict[Tuple[ServiceKey, ServiceVersion], ServiceDockerData],
) -> None:

    services_repo = ServicesRepository(connection)
    for service_key, service_version in service_keys:
        service: ServiceDockerData = services[(service_key, service_version)]
        # find the service owner
        owner_gid, service_access_rights = await _create_service_default_access_rights(
            app, service, connection)
        # set the service in the DB
        await services_repo.create_service(
            ServiceMetaDataAtDB(**service.dict(), owner=owner_gid),
            service_access_rights,
        )
Ejemplo n.º 5
0
    async def list_service_releases(
        self,
        key: str,
        *,
        major: Optional[int] = None,
        minor: Optional[int] = None,
        limit_count: Optional[int] = None,
    ) -> List[ServiceMetaDataAtDB]:
        """Lists LAST n releases of a given service, sorted from latest first

        major, minor is used to filter as major.minor.* or major.*
        limit_count limits returned value. None or non-positive values returns all matches
        """
        if minor is not None and major is None:
            raise ValueError("Expected only major.*.* or major.minor.*")

        search_condition = services_meta_data.c.key == key
        if major is not None:
            if minor is not None:
                # All patches
                search_condition &= services_meta_data.c.version.like(
                    f"{major}.{minor}.%"
                )
            else:
                # All minor and patches
                search_condition &= services_meta_data.c.version.like(f"{major}.%")

        query = (
            sa.select([services_meta_data])
            .where(search_condition)
            .order_by(sa.desc(services_meta_data.c.version))
        )

        if limit_count and limit_count > 0:
            query = query.limit(limit_count)

        releases = []
        async with self.db_engine.acquire() as conn:
            async for row in conn.execute(query):
                releases.append(ServiceMetaDataAtDB(**row))

        return releases
Ejemplo n.º 6
0
async def test_create_services(services_repo: ServicesRepository,
                               service_catalog_faker: Callable):
    # creates fake data
    fake_service, *fake_access_rights = service_catalog_faker(
        "simcore/services/dynamic/jupyterlab",
        "1.0.0",
        team_access=None,
        everyone_access=None,
    )

    # validation
    service = ServiceMetaDataAtDB.parse_obj(fake_service)
    service_access_rights = [
        ServiceAccessRightsAtDB.parse_obj(a) for a in fake_access_rights
    ]

    new_service = await services_repo.create_service(service,
                                                     service_access_rights)

    assert new_service.dict(include=set(fake_service.keys())) == service.dict()
Ejemplo n.º 7
0
 async def get_service(
     self,
     key: str,
     version: str,
     *,
     gids: Optional[List[int]] = None,
     execute_access: Optional[bool] = None,
     write_access: Optional[bool] = None,
     product_name: Optional[str] = None,
 ) -> Optional[ServiceMetaDataAtDB]:
     query = sa.select([services_meta_data]).where(
         (services_meta_data.c.key == key)
         & (services_meta_data.c.version == version)
     )
     if gids or execute_access or write_access:
         query = (
             sa.select([services_meta_data])
             .select_from(services_meta_data.join(services_access_rights))
             .where(
                 and_(
                     (services_meta_data.c.key == key),
                     (services_meta_data.c.version == version),
                     or_(*[services_access_rights.c.gid == gid for gid in gids])
                     if gids
                     else True,
                     services_access_rights.c.execute_access
                     if execute_access
                     else True,
                     services_access_rights.c.write_access if write_access else True,
                     (services_access_rights.c.product_name == product_name)
                     if product_name
                     else True,
                 )
             )
         )
     async with self.db_engine.acquire() as conn:
         row: RowProxy = await (await conn.execute(query)).first()
     if row:
         return ServiceMetaDataAtDB(**row)
Ejemplo n.º 8
0
    async def list_services(
        self,
        *,
        gids: Optional[List[int]] = None,
        execute_access: Optional[bool] = None,
        write_access: Optional[bool] = None,
        combine_access_with_and: Optional[bool] = True,
        product_name: Optional[str] = None,
    ) -> List[ServiceMetaDataAtDB]:
        services_in_db = []

        async with self.db_engine.acquire() as conn:
            async for row in conn.execute(
                _make_list_services_query(
                    gids,
                    execute_access,
                    write_access,
                    combine_access_with_and,
                    product_name,
                )
            ):
                services_in_db.append(ServiceMetaDataAtDB(**row))
        return services_in_db
Ejemplo n.º 9
0
async def _create_services_in_db(
    app: FastAPI,
    service_keys: Set[Tuple[ServiceKey, ServiceVersion]],
    services_in_registry: Dict[Tuple[ServiceKey, ServiceVersion],
                               ServiceDockerData],
) -> None:
    """Adds a new service in the database

    Determines the access rights of each service and adds it to the database"""

    services_repo = ServicesRepository(app.state.engine)

    sorted_services = sorted(service_keys, key=lambda t: Version(t[1]))

    for service_key, service_version in sorted_services:
        service_metadata: ServiceDockerData = services_in_registry[(
            service_key, service_version)]

        # DEFAULT policies
        (
            owner_gid,
            service_access_rights,
        ) = await access_rights.evaluate_default_policy(app, service_metadata)

        # AUTO-UPGRADE PATCH policy
        inherited_access_rights = await access_rights.evaluate_auto_upgrade_policy(
            service_metadata, services_repo)

        service_access_rights += inherited_access_rights
        service_access_rights = access_rights.reduce_access_rights(
            service_access_rights)

        # set the service in the DB
        await services_repo.create_service(
            ServiceMetaDataAtDB(**service_metadata.dict(), owner=owner_gid),
            service_access_rights,
        )
Ejemplo n.º 10
0
async def modify_service(
    # pylint: disable=too-many-arguments
    user_id: int,
    service_key: constr(regex=KEY_RE),
    service_version: constr(regex=VERSION_RE),
    updated_service: ServiceUpdate,
    director_client: DirectorApi = Depends(get_director_api),
    groups_repository: GroupsRepository = Depends(get_repository(GroupsRepository)),
    services_repo: ServicesRepository = Depends(get_repository(ServicesRepository)),
    x_simcore_products_name: str = Header(None),
):
    if is_frontend_service(service_key):
        # NOTE: this is a temporary decision after discussing with OM
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Cannot update front-end services",
        )

    # check the service exists
    await director_client.get(
        f"/services/{urllib.parse.quote_plus(service_key)}/{service_version}"
    )
    # the director client already raises an exception if not found

    # get the user groups
    user_groups = await groups_repository.list_user_groups(user_id)
    if not user_groups:
        # deny access
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="You have unsufficient rights to access the service",
        )
    # check the user has write access to this service
    writable_service = await services_repo.get_service(
        service_key,
        service_version,
        gids=[group.gid for group in user_groups],
        write_access=True,
        product_name=x_simcore_products_name,
    )
    if not writable_service:
        # deny access
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="You have unsufficient rights to modify the service",
        )

    # let's modify the service then
    await services_repo.update_service(
        ServiceMetaDataAtDB(
            key=service_key,
            version=service_version,
            **updated_service.dict(exclude_unset=True),
        )
    )
    # let's modify the service access rights (they can be added/removed/modified)
    current_gids_in_db = [
        r.gid
        for r in await services_repo.get_service_access_rights(
            service_key, service_version, product_name=x_simcore_products_name
        )
    ]

    if updated_service.access_rights:
        # start by updating/inserting new entries
        new_access_rights = [
            ServiceAccessRightsAtDB(
                key=service_key,
                version=service_version,
                gid=gid,
                execute_access=rights.execute_access,
                write_access=rights.write_access,
                product_name=x_simcore_products_name,
            )
            for gid, rights in updated_service.access_rights.items()
        ]
        await services_repo.upsert_service_access_rights(new_access_rights)

        # then delete the ones that were removed
        removed_gids = [
            gid
            for gid in current_gids_in_db
            if gid not in updated_service.access_rights
        ]
        deleted_access_rights = [
            ServiceAccessRightsAtDB(
                key=service_key,
                version=service_version,
                gid=gid,
                product_name=x_simcore_products_name,
            )
            for gid in removed_gids
        ]
        await services_repo.delete_service_access_rights(deleted_access_rights)

    # now return the service
    return await get_service(
        user_id,
        service_key,
        service_version,
        director_client,
        groups_repository,
        services_repo,
        x_simcore_products_name,
    )