Skip to content

Commit

Permalink
[Projects] Add option to mark project as archived (#610)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hedingber committed Dec 20, 2020
1 parent ee1d28a commit 0dfb625
Show file tree
Hide file tree
Showing 21 changed files with 316 additions and 173 deletions.
3 changes: 2 additions & 1 deletion mlrun/api/api/endpoints/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ def list_projects(
format_: schemas.Format = Query(schemas.Format.full, alias="format"),
owner: str = None,
labels: typing.List[str] = Query(None, alias="label"),
state: schemas.ProjectState = None,
db_session: Session = Depends(deps.get_db_session),
):
return get_project_member().list_projects(db_session, owner, format_, labels)
return get_project_member().list_projects(db_session, owner, format_, labels, state)
1 change: 1 addition & 0 deletions mlrun/api/db/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ def list_projects(
owner: str = None,
format_: schemas.Format = schemas.Format.full,
labels: List[str] = None,
state: schemas.ProjectState = None,
) -> schemas.ProjectsOutput:
pass

Expand Down
3 changes: 2 additions & 1 deletion mlrun/api/db/filedb/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,10 @@ def list_projects(
owner: str = None,
format_: schemas.Format = schemas.Format.full,
labels: List[str] = None,
state: schemas.ProjectState = None,
) -> schemas.ProjectsOutput:
return self._transform_run_db_error(
self.db.list_projects, owner, format_, labels
self.db.list_projects, owner, format_, labels, state
)

def store_project(self, session, name: str, project: schemas.Project):
Expand Down
3 changes: 2 additions & 1 deletion mlrun/api/db/sqldb/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -718,8 +718,9 @@ def list_projects(
owner: str = None,
format_: mlrun.api.schemas.Format = mlrun.api.schemas.Format.full,
labels: List[str] = None,
state: mlrun.api.schemas.ProjectState = None,
) -> schemas.ProjectsOutput:
query = self._query(session, Project, owner=owner)
query = self._query(session, Project, owner=owner, state=state)
if labels:
query = self._add_labels_filter(session, query, Project, labels)
projects = []
Expand Down
33 changes: 32 additions & 1 deletion mlrun/api/initial_data.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import os
import sqlalchemy.orm
import pathlib

from mlrun.api.db.init_db import init_db
import mlrun.api.db.sqldb.db
import mlrun.api.schemas
from mlrun.api.db.session import create_session, close_session
from mlrun.utils import logger
from .utils.alembic import AlembicUtil
Expand All @@ -10,7 +13,7 @@
def init_data(from_scratch: bool = False) -> None:
logger.info("Creating initial data")

# run migrations on existing DB or create it with alembic
# run schema migrations on existing DB or create it with alembic
dir_path = pathlib.Path(os.path.dirname(os.path.realpath(__file__)))
alembic_config_path = dir_path / "alembic.ini"

Expand All @@ -20,11 +23,39 @@ def init_data(from_scratch: bool = False) -> None:
db_session = create_session()
try:
init_db(db_session)
_perform_data_migrations(db_session)
finally:
close_session(db_session)
logger.info("Initial data created")


def _perform_data_migrations(db_session: sqlalchemy.orm.Session):
# FileDB is not really a thing anymore, so using SQLDB directly
db = mlrun.api.db.sqldb.db.SQLDB("")
logger.info("Performing data migrations")
_fill_project_state(db, db_session)


def _fill_project_state(
db: mlrun.api.db.sqldb.db.SQLDB, db_session: sqlalchemy.orm.Session
):
projects = db.list_projects(db_session)
for project in projects.projects:
changed = False
if not project.spec.desired_state:
changed = True
project.spec.desired_state = mlrun.api.schemas.ProjectState.online
if not project.status.state:
changed = True
project.status.state = project.spec.desired_state
if changed:
logger.debug(
"Found project without state data. Enriching",
name=project.metadata.name,
)
db.store_project(db_session, project.metadata.name, project)


def main() -> None:
init_data()

Expand Down
2 changes: 2 additions & 0 deletions mlrun/api/schemas/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
ProjectMetadata,
ProjectSpec,
ProjectsOutput,
ProjectStatus,
ProjectState,
)
from .schedule import (
SchedulesOutput,
Expand Down
11 changes: 11 additions & 0 deletions mlrun/api/schemas/project.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import datetime
import enum
import typing

import pydantic
Expand All @@ -18,6 +19,15 @@ class Config:
extra = pydantic.Extra.allow


class ProjectState(str, enum.Enum):
online = "online"
archived = "archived"


class ProjectStatus(ObjectStatus):
state: typing.Optional[ProjectState]


class ProjectSpec(pydantic.BaseModel):
description: typing.Optional[str] = None
goals: typing.Optional[str] = None
Expand All @@ -30,6 +40,7 @@ class ProjectSpec(pydantic.BaseModel):
source: typing.Optional[str] = None
subpath: typing.Optional[str] = None
origin_url: typing.Optional[str] = None
desired_state: typing.Optional[ProjectState] = ProjectState.online

class Config:
extra = pydantic.Extra.allow
Expand Down
5 changes: 5 additions & 0 deletions mlrun/api/utils/clients/nuclio.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ def list_projects(
owner: str = None,
format_: mlrun.api.schemas.Format = mlrun.api.schemas.Format.full,
labels: typing.List[str] = None,
state: mlrun.api.schemas.ProjectState = None,
) -> mlrun.api.schemas.ProjectsOutput:
if owner:
raise NotImplementedError(
Expand All @@ -99,6 +100,10 @@ def list_projects(
raise NotImplementedError(
"Filtering nuclio projects by labels is currently not supported"
)
if state:
raise NotImplementedError(
"Filtering nuclio projects by state is currently not supported"
)
response = self._send_request_to_api("GET", "projects")
response_body = response.json()
projects = []
Expand Down
32 changes: 26 additions & 6 deletions mlrun/api/utils/projects/leader.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def ensure_project(self, session: sqlalchemy.orm.Session, name: str):
def create_project(
self, session: sqlalchemy.orm.Session, project: mlrun.api.schemas.Project
) -> mlrun.api.schemas.Project:
self._validate_project_name(project.metadata.name)
self._enrich_and_validate_before_creation(project)
self._run_on_all_followers("create_project", session, project)
return self.get_project(session, project.metadata.name)

Expand All @@ -65,6 +65,7 @@ def store_project(
name: str,
project: mlrun.api.schemas.Project,
):
self._enrich_project(project)
self._validate_project_name(name)
self._validate_body_and_path_names_matches(name, project)
self._run_on_all_followers("store_project", session, name, project)
Expand All @@ -77,6 +78,7 @@ def patch_project(
project: dict,
patch_mode: mlrun.api.schemas.PatchMode = mlrun.api.schemas.PatchMode.replace,
):
self._enrich_project_patch(project)
self._validate_body_and_path_names_matches(name, project)
self._run_on_all_followers("patch_project", session, name, project, patch_mode)
return self.get_project(session, name)
Expand All @@ -95,8 +97,11 @@ def list_projects(
owner: str = None,
format_: mlrun.api.schemas.Format = mlrun.api.schemas.Format.full,
labels: typing.List[str] = None,
state: mlrun.api.schemas.ProjectState = None,
) -> mlrun.api.schemas.ProjectsOutput:
return self._leader_follower.list_projects(session, owner, format_, labels)
return self._leader_follower.list_projects(
session, owner, format_, labels, state
)

def _start_periodic_sync(self):
# if no followers no need for sync
Expand Down Expand Up @@ -192,9 +197,8 @@ def _ensure_project_synced(
# Heuristically pick the first follower
project_follower_name = list(follower_names)[0]
project = followers_projects_map[project_follower_name][project_name]
self._leader_follower.create_project(
session, mlrun.api.schemas.Project(**project.dict())
)
self._enrich_and_validate_before_creation(project)
self._leader_follower.create_project(session, project)
except Exception as exc:
logger.warning(
"Failed creating missing project in leader",
Expand Down Expand Up @@ -232,8 +236,9 @@ def _ensure_project_synced(
project=project,
)
try:
self._enrich_and_validate_before_creation(project)
self._followers[missing_follower].create_project(
session, mlrun.api.schemas.Project(**project.dict()),
session, project,
)
except Exception as exc:
logger.warning(
Expand Down Expand Up @@ -290,6 +295,21 @@ def _initialize_follower(
raise ValueError(f"Unknown follower name: {name}")
return followers_classes_map[name]

def _enrich_and_validate_before_creation(self, project: mlrun.api.schemas.Project):
self._enrich_project(project)
self._validate_project_name(project.metadata.name)

@staticmethod
def _enrich_project(project: mlrun.api.schemas.Project):
project.status.state = project.spec.desired_state

@staticmethod
def _enrich_project_patch(project_patch: dict):
if project_patch.get("spec", {}).get("desired_state"):
project_patch.setdefault("status", {})["state"] = project_patch["spec"][
"desired_state"
]

@staticmethod
def _validate_project_name(name: str, raise_on_failure: bool = True) -> bool:
try:
Expand Down
1 change: 1 addition & 0 deletions mlrun/api/utils/projects/member.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,6 @@ def list_projects(
owner: str = None,
format_: mlrun.api.schemas.Format = mlrun.api.schemas.Format.full,
labels: typing.List[str] = None,
state: mlrun.api.schemas.ProjectState = None,
) -> mlrun.api.schemas.ProjectsOutput:
pass
1 change: 1 addition & 0 deletions mlrun/api/utils/projects/remotes/member.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,6 @@ def list_projects(
owner: str = None,
format_: mlrun.api.schemas.Format = mlrun.api.schemas.Format.full,
labels: typing.List[str] = None,
state: mlrun.api.schemas.ProjectState = None,
) -> mlrun.api.schemas.ProjectsOutput:
pass
7 changes: 5 additions & 2 deletions mlrun/api/utils/projects/remotes/nop.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,12 @@ def list_projects(
owner: str = None,
format_: mlrun.api.schemas.Format = mlrun.api.schemas.Format.full,
labels: typing.List[str] = None,
state: mlrun.api.schemas.ProjectState = None,
) -> mlrun.api.schemas.ProjectsOutput:
if owner or labels:
raise NotImplementedError("Filtering by owner or labels is not supported")
if owner or labels or state:
raise NotImplementedError(
"Filtering by owner, labels or state is not supported"
)
if format_ == mlrun.api.schemas.Format.full:
return mlrun.api.schemas.ProjectsOutput(
projects=list(self._projects.values())
Expand Down
1 change: 1 addition & 0 deletions mlrun/db/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ def list_projects(
owner: str = None,
format_: schemas.Format = schemas.Format.full,
labels: List[str] = None,
state: schemas.ProjectState = None,
) -> schemas.ProjectsOutput:
pass

Expand Down
3 changes: 2 additions & 1 deletion mlrun/db/filedb.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,8 +417,9 @@ def list_projects(
owner: str = None,
format_: mlrun.api.schemas.Format = mlrun.api.schemas.Format.full,
labels: List[str] = None,
state: mlrun.api.schemas.ProjectState = None,
) -> mlrun.api.schemas.ProjectsOutput:
if owner or format_ == mlrun.api.schemas.Format.full or labels:
if owner or format_ == mlrun.api.schemas.Format.full or labels or state:
raise NotImplementedError()
run_dir = path.join(self.dirpath, run_logs)
if not path.isdir(run_dir):
Expand Down
4 changes: 4 additions & 0 deletions mlrun/db/httpdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -971,9 +971,13 @@ def list_projects(
owner: str = None,
format_: mlrun.api.schemas.Format = mlrun.api.schemas.Format.full,
labels: List[str] = None,
state: mlrun.api.schemas.ProjectState = None,
) -> List[Union[mlrun.projects.MlrunProject, str]]:
if isinstance(state, mlrun.api.schemas.ProjectState):
state = state.value
params = {
"owner": owner,
"state": state,
"format": format_,
"label": labels or [],
}
Expand Down
1 change: 1 addition & 0 deletions mlrun/db/sqldb.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ def list_projects(
owner: str = None,
format_: mlrun.api.schemas.Format = mlrun.api.schemas.Format.full,
labels: List[str] = None,
state: mlrun.api.schemas.ProjectState = None,
) -> mlrun.api.schemas.ProjectsOutput:
raise NotImplementedError()

Expand Down
3 changes: 3 additions & 0 deletions mlrun/projects/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
RunNotifications,
)
from ..runtimes.utils import add_code_metadata
import mlrun.api.schemas


class ProjectError(Exception):
Expand Down Expand Up @@ -224,6 +225,7 @@ def __init__(
subpath=None,
origin_url=None,
goals=None,
desired_state=mlrun.api.schemas.ProjectState.online.value,
):
self.repo = None

Expand All @@ -235,6 +237,7 @@ def __init__(
self.subpath = subpath or ""
self.origin_url = origin_url or ""
self.goals = goals
self.desired_state = desired_state
self.branch = None
self.tag = ""
self.params = params or {}
Expand Down

0 comments on commit 0dfb625

Please sign in to comment.