diff --git a/k8s/Chart.yaml b/k8s/Chart.yaml index 12954db09..581566d9b 100644 --- a/k8s/Chart.yaml +++ b/k8s/Chart.yaml @@ -12,7 +12,7 @@ version: 0.12.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. -appVersion: 0.13.0 +appVersion: 0.13.1 # Chart dependencies dependencies: diff --git a/lifemonitor/api/models/repositories/github.py b/lifemonitor/api/models/repositories/github.py index 0d7a15a62..18ccd4d0d 100644 --- a/lifemonitor/api/models/repositories/github.py +++ b/lifemonitor/api/models/repositories/github.py @@ -206,8 +206,13 @@ def remote_url(self) -> str: @property def owner(self) -> str: - onwer = super().owner - return onwer.login if onwer else None + owner = super().owner + return owner.login if owner else None + + @property + def owner_id(self) -> int: + owner = super().owner + return owner.id if owner else None @property def license(self) -> Optional[str]: diff --git a/lifemonitor/api/models/workflows.py b/lifemonitor/api/models/workflows.py index 7dc723088..9554dbee8 100644 --- a/lifemonitor/api/models/workflows.py +++ b/lifemonitor/api/models/workflows.py @@ -89,6 +89,10 @@ def get_registry_identifier(self, registry: WorkflowRegistry) -> str: return identifier return None + @hybrid_property + def earliest_version(self) -> WorkflowVersion: + return min(self.versions.values(), key=lambda v: v.created) + @hybrid_property def latest_version(self) -> WorkflowVersion: return max(self.versions.values(), key=lambda v: v.created) diff --git a/lifemonitor/integrations/github/controllers.py b/lifemonitor/integrations/github/controllers.py index 874bbf2d7..b80b00ff2 100644 --- a/lifemonitor/integrations/github/controllers.py +++ b/lifemonitor/integrations/github/controllers.py @@ -41,9 +41,7 @@ from lifemonitor.api.models.testsuites.testinstance import TestInstance from lifemonitor.api.models.wizards import QuestionStep, UpdateStep from lifemonitor.api.models.workflows import WorkflowVersion -from lifemonitor.auth.models import User -from lifemonitor.auth.oauth2.client.models import OAuthIdentity -from lifemonitor.auth.services import authorized, current_user +from lifemonitor.auth.services import User, authorized, current_user from lifemonitor.integrations.github import pull_requests from lifemonitor.integrations.github.app import LifeMonitorGithubApp from lifemonitor.integrations.github.events import (GithubEvent, @@ -249,12 +247,17 @@ def __notify_workflow_version_event__(repo_reference: GithubRepositoryReference, logger.debug("Notifications enabled: %r", notification_enabled) if notification_enabled: logger.debug(f"Setting notification for action '{action}' on repo '{repo.full_name}' (ref: {repo.ref})") - identity = OAuthIdentity.find_by_provider_user_id(str(repo.owner.id), "github") - if identity: + submitter = None + if isinstance(workflow_version, WorkflowVersion): + submitter = workflow_version.submitter + else: + sender = repo_reference.event.sender + submitter = sender.user if sender else None + if submitter: version = workflow_version if isinstance(workflow_version, dict) else serializers.WorkflowVersionSchema(exclude=('meta', 'links')).dump(workflow_version) repo_data = repo.raw_data repo_data['ref'] = repo.ref - n = GithubWorkflowVersionNotification(workflow_version=version, repository=repo_data, action=action, users=[identity.user]) + n = GithubWorkflowVersionNotification(workflow_version=version, repository=repo_data, action=action, users=[submitter]) n.save() logger.debug(f"Setting notification for action '{action}' on repo '{repo.full_name}' (ref: {repo.ref})") else: @@ -834,7 +837,7 @@ def handle_event(): logger.debug(str(e)) # check the author of the current pull_request - if event.pusher == event.application.bot: + if event.pusher_name == event.application.bot: logger.debug("Nothing to do: commit pushed by LifeMonitor[Bot]") return f"Push created by {event.application.bot}", 204 diff --git a/lifemonitor/integrations/github/events.py b/lifemonitor/integrations/github/events.py index abf8e7940..473addefa 100644 --- a/lifemonitor/integrations/github/events.py +++ b/lifemonitor/integrations/github/events.py @@ -95,9 +95,13 @@ def headers(self) -> dict: return self._headers @property - def pusher(self) -> str: + def pusher_name(self) -> str: return self._raw_data['pusher']['name'] if 'pusher' in self._raw_data else None + @property + def sender_name(self) -> str: + return self._raw_data['sender']['login'] if 'sender' in self._raw_data else None + @property def sender(self) -> OAuthIdentity: if not self._sender: diff --git a/lifemonitor/integrations/github/services.py b/lifemonitor/integrations/github/services.py index 6a033b227..032cdb19a 100644 --- a/lifemonitor/integrations/github/services.py +++ b/lifemonitor/integrations/github/services.py @@ -131,78 +131,91 @@ def register_repository_workflow(repository_reference: GithubRepositoryReference # registered_workflow = None - try: - # set a reference to the Gihub hosting service instance - hosting_service: HostingService = repository_reference.hosting_service - logger.debug("Hosting service: %r", hosting_service) - # set the workflow version name - workflow_version = repository_reference.branch or repository_reference.tag - # search user identity - identity: OAuthIdentity = hosting_service.server_credentials\ - .find_identity_by_provider_user_id(str(repository_reference.owner_id)) - repo_owner = identity.user - logger.debug("Workflow Submitter: %r", repo_owner) - # set the repo link - repo_link = f"{hosting_service.uri}/{repo.full_name}.git" - logger.debug("Workflow RepoLink: %s", repo_link) - # found and update the existing workflows associated with - workflow = github_registry.find_workflow(repo.full_name) - if workflow: - logger.debug("Updating workflow: %r", workflow) - current_wv = wv = workflow.versions.get(workflow_version, None) - - # initialize registries map - registries_map = __get_registries_map__(workflow, registries=registries) - - logger.debug("Created registries map: %r", registries_map) - # register or update the workflow version - if not wv: - logger.debug("Registering workflow version on worlflow: %r ....", workflow) - wv = lm.register_workflow(repo_link, repo_owner, workflow_version, - workflow_uuid=workflow.uuid, - name=repo.config.workflow_name, public=repo.config.public) - logger.debug("Registering workflow version on worlflow: %r .... DONE", workflow) - else: - logger.debug("Updating workflow version: %r...", wv) - wv = lm.update_workflow(wv.submitter, workflow.uuid, workflow_version, - name=repo.config.workflow_name, - rocrate_or_link=repo_link, public=repo.config.public) - logger.debug("Updating workflow version: %r... DONE", wv) - - # register workflow on registries - logger.debug("(old,new) workflows: (%r, %r)", current_wv, wv) - if current_wv != wv: - register_workflow_on_registries(github_registry, repo_owner, repo, wv, registries_map) - else: - # register workflow on new registries if any - registries_list = [r for r in registries_map if r[0] not in wv.registry_workflow_versions] if registries_map else None - if registries_list: - if current_wv != wv or len(registries_list) > 0: - register_workflow_on_registries(github_registry, repo_owner, repo, wv, registries_list) - else: - logger.warning("Skipped registration of workflow %r on registries %r", wv, registries_list) - # append to the list of registered workflows - registered_workflow = wv - # if no matches found, register a new workflow - else: - # register workflow version on LifeMonitor - wv = lm.register_workflow(repo_link, repo_owner, workflow_version, - name=repo.config.workflow_name, public=repo.config.public) - # register workflow on registries - register_workflow_on_registries(github_registry, repo_owner, repo, wv, registries_map=[(_, None, []) for _ in registries]) - # append to the list of registered workflows - registered_workflow = wv + # set a reference to the Gihub hosting service instance + hosting_service: HostingService = repository_reference.hosting_service + logger.debug("Hosting service: %r", hosting_service) - # register workflow version on github registry - if registered_workflow: - github_registry.add_workflow_version(registered_workflow, repo.full_name, repo.ref) - github_registry.save() + # set the workflow version name + workflow_version = repository_reference.branch or repository_reference.tag + # search user identity + submitter = None + try: + identity: OAuthIdentity = repository_reference.event.sender + submitter = identity.user + logger.debug("Found a Github identity for the sender %r --> %r, %r", repository_reference.event.sender, identity, submitter) except OAuthIdentityNotFoundException as e: - logger.warning("Github identity '%r' doesn't match with any LifeMonitor user identity", repository_reference.owner_id) + logger.warning("Github identity of the sender '%r' doesn't match with any LifeMonitor user identity", repository_reference.owner_id) if logger.isEnabledFor(logging.DEBUG): logger.exception(e) + # set the repo link + repo_link = f"{hosting_service.uri}/{repo.full_name}.git" + logger.debug("Workflow RepoLink: %s", repo_link) + + # found and update the existing workflows associated with + workflow = github_registry.find_workflow(repo.full_name) + if workflow: + logger.debug("Found workflow associated with the repo: %r", workflow) + + # fallback the submitter to the original submitter of the workflow + # (i.e., the user who registered the LifeMonitor Github app on the github repository) + if not submitter: + submitter = workflow.earliest_version.submitter + + # look up the existing workflow version + current_wv = wv = workflow.versions.get(workflow_version, None) + + # initialize registries map + registries_map = __get_registries_map__(workflow, registries=registries) + + logger.debug("Created registries map: %r", registries_map) + # register or update the workflow version + if not wv: + logger.debug("Registering workflow version on worlflow: %r ....", workflow) + wv = lm.register_workflow(repo_link, submitter, workflow_version, + workflow_uuid=workflow.uuid, + name=repo.config.workflow_name, public=repo.config.public) + logger.debug("Registering workflow version on worlflow: %r .... DONE", workflow) + else: + logger.debug("Updating workflow version: %r...", wv) + wv = lm.update_workflow(wv.submitter, workflow.uuid, workflow_version, + name=repo.config.workflow_name, + rocrate_or_link=repo_link, public=repo.config.public) + logger.debug("Updating workflow version: %r... DONE", wv) + + # register workflow on registries + logger.debug("(old,new) workflows: (%r, %r)", current_wv, wv) + if current_wv != wv: + register_workflow_on_registries(github_registry, submitter, repo, wv, registries_map) + else: + # register workflow on new registries if any + registries_list = [r for r in registries_map if r[0] not in wv.registry_workflow_versions] if registries_map else None + if registries_list: + if current_wv != wv or len(registries_list) > 0: + register_workflow_on_registries(github_registry, submitter, repo, wv, registries_list) + else: + logger.warning("Skipped registration of workflow %r on registries %r", wv, registries_list) + # append to the list of registered workflows + registered_workflow = wv + # if no matches found, register a new workflow + elif submitter: + # register workflow version on LifeMonitor + wv = lm.register_workflow(repo_link, submitter, workflow_version, + name=repo.config.workflow_name, public=repo.config.public) + # register workflow on registries + register_workflow_on_registries(github_registry, submitter, repo, wv, registries_map=[(_, None, []) for _ in registries]) + # append to the list of registered workflows + registered_workflow = wv + else: + logger.error("Unable to register workflow version '%r': " + "unable to associate a LifeMonitor user to the repository submitter", workflow_version) + + # register workflow version on github registry + if registered_workflow: + github_registry.add_workflow_version(registered_workflow, repo.full_name, repo.ref) + github_registry.save() + return registered_workflow @@ -215,68 +228,77 @@ def delete_repository_workflow_version(repository_reference: GithubRepositoryRef repo: GithubWorkflowRepository = repository_reference.repository logger.debug("Repository: %r", repo) + # set reference to the github workflow registry + github_registry: GithubWorkflowRegistry = repository_reference.event.installation.github_registry + + # set a reference to the Github hosting service instance + hosting_service: HostingService = repository_reference.hosting_service + logger.debug("Hosting service: %r", hosting_service) + + # set the workflow version name + workflow_version = repository_reference.branch or repository_reference.tag + + # search user identity + submitter = None try: - # set reference to the github workflow registry - github_registry: GithubWorkflowRegistry = repository_reference.event.installation.github_registry - # set a reference to the Gihub hosting service instance - hosting_service: HostingService = repository_reference.hosting_service - logger.debug("Hosting service: %r", hosting_service) - # set the workflow version name - workflow_version = repository_reference.branch or repository_reference.tag - # search user identity - identity: OAuthIdentity = hosting_service.server_credentials\ - .find_identity_by_provider_user_id(str(repository_reference.owner_id)) - repo_owner = identity.user - # set the repo link - repo_link = f"{hosting_service.uri}/{repo.full_name}.git" - logger.debug("RepoLink: %s", repo_link) - - # Try to delete the workflow from registries only if it has only one version. - # Deletion of a single workflow version is not supported at the moment - # due to the limitation of the supported registry API (i.e., Seek) - w = github_registry.find_workflow(repo.full_name) - if not w: - logger.warning(f"No workflow associated with '{repo.full_name}' found") - else: - # try to find the workflow version - wv = lm.get_user_workflow_version(repo_owner, w.uuid, workflow_version) - if not wv: - logger.warning(f"Unable to find the version {workflow_version} of workflow {w.uuid}") - return None - else: - # serialize the workflow version object before deletion - wv = serializers.WorkflowVersionSchema(exclude=('meta', 'links')).dump(wv) + identity: OAuthIdentity = repository_reference.event.sender + submitter = identity.user + except OAuthIdentityNotFoundException as e: + logger.warning("Github identity of the sender '%r' doesn't match with any LifeMonitor user identity", repository_reference.owner_id) + if logger.isEnabledFor(logging.DEBUG): + logger.exception(e) - # normalize the list of registries - registries = __normalize_registry_identitiers__(registries, as_strings=True) - logger.debug("Normalized list of registries: %r", registries) + # set the repo link + repo_link = f"{hosting_service.uri}/{repo.full_name}.git" + logger.debug("RepoLink: %s", repo_link) + + # Try to delete the workflow from registries only if it has only one version. + # Deletion of a single workflow version is not supported at the moment + # due to the limitation of the supported registry API (i.e., Seek) + w = github_registry.find_workflow(repo.full_name) + if not w: + logger.warning(f"No workflow associated with '{repo.full_name}' found") + else: + + # fallback the submitter to the original submitter of the workflow + # (i.e., the user who registered the LifeMonitor Github app on the github repository) + if not submitter: + submitter = w.earliest_version.submitter + + # try to find the workflow version + wv = lm.get_user_workflow_version(submitter, w.uuid, workflow_version) + if not wv: + logger.warning(f"Unable to find the version {workflow_version} of workflow {w.uuid}") + return None + else: + # serialize the workflow version object before deletion + wv = serializers.WorkflowVersionSchema(exclude=('meta', 'links')).dump(wv) - # initialize registries map - registry_workflows_map = __get_registries_map__(w, registries=registries) - logger.debug("List of registries for wf %r: %r", w, registry_workflows_map) + # normalize the list of registries + registries = __normalize_registry_identitiers__(registries, as_strings=True) + logger.debug("Normalized list of registries: %r", registries) - # filter workflows with only one version - filtered_registries = [_ for _ in registry_workflows_map if len(_[2]) == 1] - logger.debug("Filtered registry workflows: %r", filtered_registries) + # initialize registries map + registry_workflows_map = __get_registries_map__(w, registries=registries) + logger.debug("List of registries for wf %r: %r", w, registry_workflows_map) - # delete workflow from registries - logger.debug("Removing version '%r' of worlflow %r from registries %r....", workflow_version, w, filtered_registries) - delete_workflow_from_registries(github_registry, repo_owner, w, filtered_registries) - logger.debug("Removing version '%r' of worlflow %r from registries %r.... DONE", workflow_version, w, filtered_registries) + # filter workflows with only one version + filtered_registries = [_ for _ in registry_workflows_map if len(_[2]) == 1] + logger.debug("Filtered registry workflows: %r", filtered_registries) - # delete workflow version from LifeMonitor - logger.debug("Removing version '%r' of worlflow %r from LifeMonitor....", workflow_version, w) + # delete workflow from registries + logger.debug("Removing version '%r' of worlflow %r from registries %r....", workflow_version, w, filtered_registries) + delete_workflow_from_registries(github_registry, submitter, w, filtered_registries) + logger.debug("Removing version '%r' of worlflow %r from registries %r.... DONE", workflow_version, w, filtered_registries) - lm.deregister_user_workflow_version(w.uuid, workflow_version, repo_owner) - logger.debug("Removing version '%r' of worlflow %r from LifeMonitor.... DONE", workflow_version, w) + # delete workflow version from LifeMonitor + logger.debug("Removing version '%r' of worlflow %r from LifeMonitor....", workflow_version, w) - # return the deleted workflow version (serialized) - return wv + lm.deregister_user_workflow_version(w.uuid, workflow_version, submitter) + logger.debug("Removing version '%r' of worlflow %r from LifeMonitor.... DONE", workflow_version, w) - except OAuthIdentityNotFoundException as e: - logger.warning("Github identity '%r' doesn't match with any LifeMonitor user identity", repository_reference.owner_id) - if logger.isEnabledFor(logging.DEBUG): - logger.exception(e) + # return the deleted workflow version (serialized) + return wv return None diff --git a/lifemonitor/static/src/package.json b/lifemonitor/static/src/package.json index 9fac52eb3..98affc22d 100644 --- a/lifemonitor/static/src/package.json +++ b/lifemonitor/static/src/package.json @@ -1,7 +1,7 @@ { "name": "lifemonitor", "description": "Workflow Testing Service", - "version": "0.13.0", + "version": "0.13.1", "license": "MIT", "author": "CRS4", "main": "../dist/js/lifemonitor.min.js", diff --git a/specs/api.yaml b/specs/api.yaml index e93769681..52749572e 100644 --- a/specs/api.yaml +++ b/specs/api.yaml @@ -3,7 +3,7 @@ openapi: "3.0.0" info: - version: "0.13.0" + version: "0.13.1" title: "Life Monitor API" description: | *Workflow sustainability service* @@ -18,7 +18,7 @@ info: servers: - url: / description: > - Version 0.13.0 of API. + Version 0.13.1 of API. tags: - name: GitHub Integration