Skip to content

Commit

Permalink
release v0.10.0
Browse files Browse the repository at this point in the history
  • Loading branch information
kikkomep committed May 5, 2023
2 parents 50cdb79 + d58c3a2 commit 48a69dc
Show file tree
Hide file tree
Showing 34 changed files with 285 additions and 99 deletions.
3 changes: 2 additions & 1 deletion cli/client/issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ def check(config, repository, output_path):
console.print(f"[{message.type.value}]{message.type.name}:[/{message.type.value}{message.text}")
console.print("\n\n")
except Exception as e:
logger.exception(e)
if logger.isEnabledFor(logging.DEBUG):
logger.exception(e)
error_console.print(str(e))


Expand Down
25 changes: 15 additions & 10 deletions cli/client/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from urllib.parse import urlparse

from lifemonitor.api.models.repositories.github import GithubWorkflowRepository
from lifemonitor.api.models.repositories.local import LocalWorkflowRepository
from lifemonitor.api.models.repositories.local import LocalWorkflowRepository, LocalGitRepository
from rich.prompt import Prompt

# Set module logger
Expand All @@ -42,15 +42,20 @@ def is_url(value):

def get_repository(repository: str, local_path: str):
assert repository, repository
if is_url(repository):
remote_repo_url = repository
if remote_repo_url.endswith('.git'):
return GithubWorkflowRepository.from_url(remote_repo_url, auto_cleanup=False, local_path=local_path)
else:
local_copy_path = os.path.join(local_path, os.path.basename(repository))
shutil.copytree(repository, local_copy_path)
return LocalWorkflowRepository(local_copy_path)
raise ValueError("Repository type not supported")
try:
if is_url(repository):
remote_repo_url = repository
if remote_repo_url.endswith('.git'):
return GithubWorkflowRepository.from_url(remote_repo_url, auto_cleanup=False, local_path=local_path)
else:
local_copy_path = os.path.join(local_path, os.path.basename(repository))
shutil.copytree(repository, local_copy_path)
if LocalGitRepository.is_git_repo(local_copy_path):
return LocalGitRepository(local_copy_path)
return LocalWorkflowRepository(local_copy_path)
raise ValueError("Repository type not supported")
except Exception as e:
raise ValueError("Error while loading the repository: %s" % e)


def init_output_path(output_path):
Expand Down
13 changes: 8 additions & 5 deletions lifemonitor/api/models/registries/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,21 @@
from abc import ABC, abstractmethod
from typing import List, Tuple, Union

import lifemonitor.api.models as models
import lifemonitor.exceptions as lm_exceptions
import requests
from authlib.integrations.base_client import RemoteApp
from sqlalchemy.orm.collections import attribute_mapped_collection
from sqlalchemy.orm.exc import NoResultFound

import lifemonitor.api.models as models
import lifemonitor.exceptions as lm_exceptions
from lifemonitor import utils as lm_utils
from lifemonitor.api.models import db
from lifemonitor.api.models.repositories.base import WorkflowRepository
from lifemonitor.auth import models as auth_models
from lifemonitor.auth.models import Resource
from lifemonitor.auth.oauth2.client.models import OAuthIdentity
from lifemonitor.auth.oauth2.client.services import oauth2_registry
from lifemonitor.utils import ClassManager, download_url
from sqlalchemy.orm.collections import attribute_mapped_collection
from sqlalchemy.orm.exc import NoResultFound
from lifemonitor.utils import ClassManager, download_url, is_service_alive

# set module level logger
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -440,6 +441,8 @@ def delete_workflow(self, submitter: auth_models.User, external_id: str) -> bool

@property
def client(self) -> WorkflowRegistryClient:
if not is_service_alive(self.uri):
raise lm_exceptions.UnavailableServiceException(f"Service {self.uri} is not available", service=self)
if self._client is None:
rtype = self.__class__.__name__.replace("WorkflowRegistry", "").lower()
return WorkflowRegistryClient.get_client_class(rtype)(self)
Expand Down
10 changes: 8 additions & 2 deletions lifemonitor/api/models/repositories/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,11 +297,17 @@ def config(self) -> WorkflowRepositoryConfig:
pass
return self._config

def generate_config(self, ignore_existing=False) -> WorkflowFile:
def generate_config(self, ignore_existing=False,
workflow_title: Optional[str] = None,
public: bool = False, main_branch: Optional[str] = None) -> WorkflowFile:
current_config = self.config
if current_config and not ignore_existing:
raise IllegalStateException("Config exists")
self._config = WorkflowRepositoryConfig.new(self.local_path, workflow_title=self.metadata.main_entity_name if self.metadata else None)
self._config = WorkflowRepositoryConfig.new(self.local_path,
workflow_title=workflow_title if workflow_title is not None
else self.metadata.main_entity_name if self.metadata else None,
main_branch=main_branch if main_branch else getattr(self, "main_branch", "main"),
public=public)
return self._config

def write_zip(self, target_path: str):
Expand Down
17 changes: 17 additions & 0 deletions lifemonitor/api/models/repositories/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,20 @@ def __init__(self, base64_rocrate: str) -> None:
except Exception as e:
logger.debug(e)
raise DecodeROCrateException(detail=str(e))


class LocalGitRepository(LocalWorkflowRepository):

def __init__(self, local_path: str | None = None, exclude: List[str] | None = None) -> None:
super().__init__(local_path, exclude)
assert self.is_git_repo(self.local_path), f"Local path {local_path} is not a git repository"

@property
def main_branch(self) -> str:
from git import Repo
repo = Repo(self.local_path)
return repo.active_branch.name

@staticmethod
def is_git_repo(local_path: str) -> bool:
return os.path.isdir(os.path.join(local_path, '.git'))
40 changes: 20 additions & 20 deletions lifemonitor/api/models/rocrate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,13 @@
from pathlib import Path
from typing import Dict, List, Optional, Tuple

import lifemonitor.exceptions as lm_exceptions
from flask import current_app
from github.GithubException import GithubException, RateLimitExceededException
from rocrate import rocrate
from sqlalchemy import inspect
from sqlalchemy.ext.hybrid import hybrid_property

import lifemonitor.exceptions as lm_exceptions
from lifemonitor.api.models import db, repositories
from lifemonitor.api.models.repositories.base import (
WorkflowRepository, WorkflowRepositoryMetadata)
Expand All @@ -43,10 +47,6 @@
from lifemonitor.models import JSON
from lifemonitor.storage import RemoteStorage
from lifemonitor.utils import download_url, get_current_ref
from sqlalchemy import inspect
from sqlalchemy.ext.hybrid import hybrid_property

from rocrate import rocrate

# set module level logger
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -111,7 +111,7 @@ def storage_path(self) -> str:

@property
def revision(self) -> Optional[GithubRepositoryRevision]:
if self._is_github_crate_(self.uri) and isinstance(self.repository, GithubWorkflowRepository):
if self._get_normalized_github_url_(self.uri) and isinstance(self.repository, GithubWorkflowRepository):
return self.repository.get_revision(self.version)
return None

Expand Down Expand Up @@ -172,7 +172,7 @@ def repository(self) -> repositories.WorkflowRepository:
logger.warning(f"Getting path {self.storage_path} from remote storage.... DONE!!!")

# instantiate a local ROCrate repository
if self._is_github_crate_(self.uri):
if self._get_normalized_github_url_(self.uri):
authorizations = self.authorizations + [None]
token = None
for authorization in authorizations:
Expand All @@ -192,8 +192,13 @@ def repository(self) -> repositories.WorkflowRepository:
self._repository = repositories.ZippedWorkflowRepository(self.local_path)

# set metadata
self._metadata = self._repository.metadata.to_json()
self._metadata_loaded = True
try:
self._metadata = self._repository.metadata.to_json()
self._metadata_loaded = True
except Exception as e:
if logger.isEnabledFor(logging.DEBUG):
logger.exception(e)
raise lm_exceptions.NotValidROCrateException("Unable to load ROCrate metadata")
return self._repository

@property
Expand All @@ -215,13 +220,13 @@ def __get_attribute_from_crate_reader__(self,
if logger.isEnabledFor(logging.DEBUG):
logger.exception(e)
if not ignore_errors:
raise e
raise
except Exception as e:
logger.error(str(e))
if logger.isEnabledFor(logging.DEBUG):
logger.exception(e)
if not ignore_errors:
raise e
raise
return None

def get_authors(self, suite_id: str = None) -> List[Dict] | None:
Expand Down Expand Up @@ -284,14 +289,9 @@ def download(self, target_path: str) -> str:
return (tmpdir_path / 'rocrate.zip').as_posix()

@staticmethod
def _is_github_crate_(uri: str) -> str:
# FIXME: replace with a better detection mechanism
if uri.startswith('https://github.com'):
# normalize uri as clone URL
if not uri.endswith('.git'):
uri += '.git'
return uri
return None
def _get_normalized_github_url_(uri: str) -> Optional[str]:
from lifemonitor.integrations.github.utils import normalized_github_url
return normalized_github_url(uri)

@staticmethod
def _find_git_ref_(repo: GithubWorkflowRepository, version: str) -> str:
Expand Down Expand Up @@ -327,7 +327,7 @@ def download_from_source(self, target_path: str = None, uri: str = None, version
# try either with authorization header and without authorization
for authorization in self._get_authorizations(extra_auth=extra_auth):
try:
git_url = self._is_github_crate_(uri)
git_url = self._get_normalized_github_url_(uri)
if git_url:
token = None
if authorization and isinstance(authorization, ExternalServiceAuthorizationHeader):
Expand Down
4 changes: 2 additions & 2 deletions lifemonitor/api/models/rocrate/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ def generate_crate(workflow_type: str,
workflow_version: str,
local_repo_path: str,
repo_url: Optional[str],
license: Optional[str] = "MIT",
ci_workflow: Optional[str] = "main.yml",
license: Optional[str] = None,
ci_workflow: Optional[str] = None,
lang_version: Optional[str] = None,
**kwargs):
make_crate = get_crate_generator(workflow_type)
Expand Down
11 changes: 10 additions & 1 deletion lifemonitor/api/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from lifemonitor.auth.oauth2.client.models import OAuthIdentity
from lifemonitor.auth.oauth2.server import server
from lifemonitor.tasks.models import Job
from lifemonitor.utils import OpenApiSpecs, ROCrateLinkContext, to_snake_case
from lifemonitor.utils import OpenApiSpecs, ROCrateLinkContext, is_service_alive, to_snake_case
from lifemonitor.ws import io

logger = logging.getLogger()
Expand All @@ -58,6 +58,9 @@ def __init__(self):
def _find_and_check_shared_workflow_version(user: User, uuid, version=None) -> models.WorkflowVersion:
for svc in models.WorkflowRegistry.all():
try:
if not is_service_alive(svc.uri):
logger.warning(f"Service {svc.uri} is not alive")
continue
if svc.get_user(user.id):
for w in svc.get_user_workflows(user):
if str(w.uuid) == str(uuid):
Expand Down Expand Up @@ -559,6 +562,9 @@ def get_user_workflows(user: User,

workflows = [w for w in models.Workflow.get_user_workflows(user, include_subscriptions=include_subscriptions)]
for svc in models.WorkflowRegistry.all():
if not is_service_alive(svc.uri):
logger.warning("Service %r is not alive, skipping", svc.uri)
continue
if svc.get_user(user.id):
try:
workflows.extend([w for w in svc.get_user_workflows(user)
Expand All @@ -570,6 +576,9 @@ def get_user_workflows(user: User,
@staticmethod
def get_user_registry_workflows(user: User, registry: models.WorkflowRegistry) -> List[models.Workflow]:
workflows = []
if not is_service_alive(registry.uri):
logger.warning("Service %r is not alive, skipping", registry.uri)
raise lm_exceptions.UnavailableServiceException(registry.uri, service=registry)
if registry.get_user(user.id):
try:
workflows.extend([w for w in registry.get_user_workflows(user)
Expand Down
10 changes: 6 additions & 4 deletions lifemonitor/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
logger = logging.getLogger(__name__)


def create_app(env=None, settings=None, init_app=True, worker=False, load_jobs=True, **kwargs):
def create_app(env=None, settings=None, init_app=True, init_integrations=True,
worker=False, load_jobs=True, **kwargs):
"""
App factory method
:param env:
Expand Down Expand Up @@ -81,7 +82,7 @@ def create_app(env=None, settings=None, init_app=True, worker=False, load_jobs=T
# initialize the application
if init_app:
with app.app_context() as ctx:
initialize_app(app, ctx, load_jobs=load_jobs)
initialize_app(app, ctx, load_jobs=load_jobs, load_integrations=init_integrations)

@app.route("/")
def index():
Expand Down Expand Up @@ -127,7 +128,7 @@ def log_response(response):
return app


def initialize_app(app: Flask, app_context, prom_registry=None, load_jobs: bool = True):
def initialize_app(app: Flask, app_context, prom_registry=None, load_jobs: bool = True, load_integrations: bool = True):
# init tmp folder
os.makedirs(app.config.get('BASE_TEMP_FOLDER'), exist_ok=True)
# enable CORS
Expand All @@ -151,7 +152,8 @@ def initialize_app(app: Flask, app_context, prom_registry=None, load_jobs: bool
# init mail system
init_mail(app)
# initialize integrations
init_integrations(app)
if load_integrations:
init_integrations(app)
# initialize metrics engine
init_metrics(app, prom_registry)
# register commands
Expand Down
19 changes: 14 additions & 5 deletions lifemonitor/auth/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,17 @@

import connexion
import flask
from flask import current_app, flash, redirect, render_template, request, session, url_for
from flask import (current_app, flash, redirect, render_template, request,
session, url_for)
from flask_login import login_required, login_user, logout_user

from lifemonitor.cache import Timeout, cached, clear_cache
from lifemonitor.utils import (NextRouteRegistry, next_route_aware,
split_by_crlf)

from .. import exceptions
from ..utils import OpenApiSpecs, boolean_value
from ..utils import (OpenApiSpecs, boolean_value, get_external_server_url,
is_service_alive)
from . import serializers
from .forms import (EmailForm, LoginForm, NotificationsForm, Oauth2ClientForm,
RegisterForm, SetPasswordForm)
Expand Down Expand Up @@ -190,6 +193,7 @@ def profile(form=None, passwordForm=None, currentView=None,
registrySettingsForm=registrySettingsForm or RegistrySettingsForm.from_model(current_user),
providers=get_providers(), currentView=currentView,
oauth2_generic_client_scopes=OpenApiSpecs.get_instance().authorization_code_scopes,
api_base_url=get_external_server_url(),
back_param=back_param)


Expand All @@ -210,7 +214,8 @@ def register():
clear_cache()
return redirect(url_for("auth.index"))
return render_template("auth/register.j2", form=form,
action=url_for('auth.register'), providers=get_providers())
action=url_for('auth.register'),
providers=get_providers(), is_service_available=is_service_alive)


@blueprint.route("/identity_not_found", methods=("GET", "POST"))
Expand Down Expand Up @@ -259,14 +264,16 @@ def login():
form = LoginForm()
flask.session["confirm_user_details"] = True
flask.session["sign_in"] = True
flask.session.pop('_flashes', None)
if form.validate_on_submit():
user = form.get_user()
if user:
login_user(user)
session.pop('_flashes', None)
flash("You have logged in", category="success")
return redirect(NextRouteRegistry.pop(url_for("auth.profile")))
return render_template("auth/login.j2", form=form, providers=get_providers())
return render_template("auth/login.j2", form=form,
providers=get_providers(), is_service_available=is_service_alive)


@blueprint.route("/logout")
Expand All @@ -276,7 +283,9 @@ def logout():
session.pop('_flashes', None)
flash("You have logged out", category="success")
NextRouteRegistry.clear()
return redirect('/')
next_route = request.args.get('next', '/')
logger.debug("Next route after logout: %r", next_route)
return redirect(next_route)


@blueprint.route("/delete_account", methods=("POST",))
Expand Down
Loading

0 comments on commit 48a69dc

Please sign in to comment.