Jaypore CI

> Jaypore CI: Minimal, Offline, Local CI system.
Log | Files | Refs | README

commit 59571f752d007f5d0022b72691dcf3384f0b3378
parent 5052eb6f373da9e75eafac3b444a55ce3abda400
Author: arjoonn <arjoonn@noreply.localhost>
Date:   Sun, 19 Feb 2023 08:31:33 +0000

Report to multiple remotes and add email remote (!43)

Branch auto created by JayporeCI

```jayporeci
╔ 🟢 : JayporeCI       [sha 14729bffa0]
┏━ build_and_test
┃
┃ 🟢 : JciEnv          [631400de]   5:51
┃ 🟢 : Jci             [39817329]   0:15       ❮-- ['JciEnv']
┃ 🟢 : black           [bff952d0]   0: 0       ❮-- ['JciEnv']
┃ 🟢 : pylint          [df90af0b]   0: 5       ❮-- ['JciEnv']
┃ 🟢 : pytest          [9d144b4e]   0: 1 71%   ❮-- ['JciEnv']
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Publish
┃
┃ 🟢 : DockerHubJci    [83a0a4ab]   2: 2
┃ 🟢 : DockerHubJcienv [94df1826]   2: 7
┃ 🟢 : PublishDocs     [7cec4420]   0:32
┃ 🟢 : PublishPypi     [e40c3cf6]   0: 6
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
```

Co-authored-by: arjoonn sharma <arjoonn@midpathsoftware.com>
Reviewed-on: https://gitea.midpathsoftware.com/midpath/jaypore_ci/pulls/43

Diffstat:
Mcicd/cicd.py | 7+++----
Mdocs/source/index.rst | 30++++++++++++++++++++++++------
Mjaypore_ci/interfaces.py | 27+++++++++++++++++++++++++--
Mjaypore_ci/jci.py | 71+++++++++++++++++++++--------------------------------------------------
Mjaypore_ci/remotes/__init__.py | 1+
Ajaypore_ci/remotes/email.py | 154+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mjaypore_ci/remotes/gitea.py | 41++++++++++-------------------------------
Mjaypore_ci/remotes/github.py | 41++++++++++-------------------------------
Mjaypore_ci/remotes/mock.py | 4++--
Ajaypore_ci/repos/__init__.py | 2++
Ajaypore_ci/repos/git.py | 43+++++++++++++++++++++++++++++++++++++++++++
Ajaypore_ci/repos/mock.py | 20++++++++++++++++++++
Mpyproject.toml | 2+-
Msecrets/bin/edit_env.sh | 22++++++++++++----------
Msecrets/ci.enc | 22+++++++++++++---------
Mtests/conftest.py | 9++++++---
16 files changed, 347 insertions(+), 149 deletions(-)

diff --git a/cicd/cicd.py b/cicd/cicd.py @@ -1,13 +1,12 @@ from jaypore_ci import jci - with jci.Pipeline() as p: - jcienv = f"jcienv:{p.remote.sha}" + jcienv = f"jcienv:{p.repo.sha}" with p.stage("build_and_test"): - p.job("JciEnv", f"docker build --target jcienv -t jcienv:{p.remote.sha} .") + p.job("JciEnv", f"docker build --target jcienv -t jcienv:{p.repo.sha} .") p.job( "Jci", - f"docker build --target jci -t jci:{p.remote.sha} .", + f"docker build --target jci -t jci:{p.repo.sha} .", depends_on=["JciEnv"], ) kwargs = dict(image=jcienv, depends_on=["JciEnv"]) diff --git a/docs/source/index.rst b/docs/source/index.rst @@ -190,7 +190,7 @@ codebase, then builds and publishes documentation. from jaypore_ci import jci with jci.Pipeline() as p: - image = f"myproject_{p.remote.sha}" + image = f"myproject_{p.repo.sha}" with p.stage("build"): p.job("DockDev", f"docker build --target DevEnv -t {image}_dev .") @@ -216,16 +216,16 @@ codebase, then builds and publishes documentation. ) with p.stage("publish"): - p.job("TagProd", f"docker tag -t {image}_prod hub/{image}_prod:{p.remote.sha}") - p.job("TagDev", f"docker tag -t {image}_dev hub/{image}_dev:{p.remote.sha}") + p.job("TagProd", f"docker tag -t {image}_prod hub/{image}_prod:{p.repo.sha}") + p.job("TagDev", f"docker tag -t {image}_dev hub/{image}_dev:{p.repo.sha}") p.job( "PushProd", - f"docker push hub/{image}_prod:{p.remote.sha}", + f"docker push hub/{image}_prod:{p.repo.sha}", depends_on=["TagProd"], ) p.job( "PushDev", - f"docker push hub/{image}_dev:{p.remote.sha}", + f"docker push hub/{image}_dev:{p.repo.sha}", depends_on=["TagDev"], ) p.job( @@ -339,7 +339,7 @@ different jobs. with jci.Pipeline() as p: p.job("testing", "bash cicd/lint_test_n_build.sh") - if p.remote.branch == 'main': + if p.repo.branch == 'main': p.job("publish", "bash cicd/publish_release.sh", depends_on=['testing']) @@ -373,6 +373,24 @@ would test and make sure that jobs are running in order. order = pipeline.executor.get_execution_order() assert order["x"] < order["y"] < order["z"] +Status report via email +----------------------- + +You can send pipeline status reports via email if you don't want to use the PR system for gitea/github etc. + +See the :class:`~jaypore_ci.remotes.email.Email` docs for the environment +variables you will have to supply to make this work. + +.. code-block:: python + + from jaypore_ci import jci, executors, remotes, repos + + git = repos.Git.from_env() + email = remotes.Email.from_env(repo=git) + + with jci.Pipeline(repo=git, remote=email) as p: + p.job("x", "x") + Contributing ============ diff --git a/jaypore_ci/interfaces.py b/jaypore_ci/interfaces.py @@ -5,7 +5,7 @@ Currently only gitea and docker are supported as remote and executor respectively. """ from enum import Enum -from typing import NamedTuple +from typing import NamedTuple, List class TriggerFailed(Exception): @@ -34,6 +34,29 @@ class Status(Enum): SKIPPED = 70 +class Repo: + """ + Contains information about the current VCS repo. + """ + + def __init__(self, sha: str, branch: str, remote: str): + self.sha: str = sha + self.branch: str = branch + self.remote: str = remote + + def files_changed(self, target: str) -> List[str]: + "Returns list of files changed between current sha and target" + raise NotImplementedError() + + @classmethod + def from_env(cls) -> "Repo": + """ + Creates a :class:`~jaypore_ci.interfaces.Repo` instance + from the environment and git repo on disk. + """ + raise NotImplementedError() + + class Executor: """ An executor is something used to run a job. @@ -97,7 +120,7 @@ class Remote: pass @classmethod - def from_env(cls): + def from_env(cls, *, repo: "Repo"): """ This function should create a Remote instance from the given environment. It can read git information / look at environment variables etc. diff --git a/jaypore_ci/jci.py b/jaypore_ci/jci.py @@ -12,8 +12,15 @@ from contextlib import contextmanager import structlog import pendulum -from jaypore_ci import remotes, executors, reporters -from jaypore_ci.interfaces import Remote, Executor, Reporter, TriggerFailed, Status +from jaypore_ci import remotes, executors, reporters, repos +from jaypore_ci.interfaces import ( + Remote, + Executor, + Reporter, + TriggerFailed, + Status, + Repo, +) from jaypore_ci.logging import logger TZ = "UTC" @@ -26,59 +33,17 @@ FIN_STATUSES = (Status.FAILED, Status.PASSED, Status.TIMEOUT, Status.SKIPPED) PREFIX = "JAYPORE_" -class Repo: - """ - Contains information about the current repo. - """ - - sha: str - branch: str - remote: str - - def files_changed(self, target): - "Returns list of files changed between current sha and target" - return ( - subprocess.check_output( - f"git diff --name-only {target} {self.sha}", shell=True - ) - .decode() - .strip() - .split("\n") - ) - - @classmethod - def from_env(cls): - remote = ( - subprocess.check_output( - "git remote -v | grep push | awk '{print $2}'", shell=True - ) - .decode() - .strip() - ) - assert "https://" in remote, "Only https remotes supported" - assert ".git" in remote - branch = ( - subprocess.check_output( - r"git branch | grep \* | awk '{print $2}'", shell=True - ) - .decode() - .strip() - ) - sha = subprocess.check_output("git rev-parse HEAD", shell=True).decode().strip() - return Repo(sha=sha, branch=branch, remote=remote) - - class Job: # pylint: disable=too-many-instance-attributes """ This is the fundamental building block for running jobs. Each job goes through a lifecycle defined by :class:`jaypore_ci.interfaces.Status`. - A job is run by an :class:`jaypore_ci.interfaces.Executor` as part of a - :class:`jaypore_ci.jci.Pipeline`. + A job is run by an :class:`~jaypore_ci.interfaces.Executor` as part of a + :class:`~jaypore_ci.jci.Pipeline`. It is never created manually. The correct way to create a job is to use - :meth:`jaypore_ci.jci.Pipeline.job`. + :meth:`~jaypore_ci.jci.Pipeline.job`. """ def __init__( @@ -238,10 +203,11 @@ class Pipeline: # pylint: disable=too-many-instance-attributes def __init__( # pylint: disable=too-many-arguments self, + *, + repo: Repo = None, remote: Remote = None, executor: Executor = None, reporter: Reporter = None, - *, graph_direction: str = "TB", poll_interval: int = 1, **kwargs, @@ -249,7 +215,12 @@ class Pipeline: # pylint: disable=too-many-instance-attributes self.jobs = {} self.services = [] self.should_pass_called = set() - self.remote = remote if remote is not None else remotes.gitea.Gitea.from_env() + self.repo = repo if repo is not None else repos.Git.from_env() + self.remote = ( + remote + if remote is not None + else remotes.gitea.Gitea.from_env(repo=self.repo) + ) self.executor = executor if executor is not None else executors.docker.Docker() self.reporter = reporter if reporter is not None else reporters.text.Text() self.graph_direction = graph_direction @@ -266,7 +237,7 @@ class Pipeline: # pylint: disable=too-many-instance-attributes ) self.executor.set_pipeline(self) # --- - kwargs["image"] = kwargs.get("image", "arjoonn/jaypore_ci:latest") + kwargs["image"] = kwargs.get("image", "arjoonn/jci:latest") kwargs["timeout"] = kwargs.get("timeout", 15 * 60) kwargs["env"] = kwargs.get("env", {}) kwargs["stage"] = "Pipeline" diff --git a/jaypore_ci/remotes/__init__.py b/jaypore_ci/remotes/__init__.py @@ -1,3 +1,4 @@ from .mock import Mock from .gitea import Gitea from .github import Github +from .email import Email diff --git a/jaypore_ci/remotes/email.py b/jaypore_ci/remotes/email.py @@ -0,0 +1,154 @@ +""" +An email remote. + +This is used to report pipeline status via email. +Multiple updates appear as a single thread. +""" +import os +import time +import smtplib +from html import escape as html_escape + +from email.headerregistry import Address +from email.message import EmailMessage +from pathlib import Path +from urllib.parse import urlparse + + +from jaypore_ci.interfaces import Remote, Repo +from jaypore_ci.logging import logger + + +class Email(Remote): # pylint: disable=too-many-instance-attributes + """ + You can send pipeline status via email using this remote. In order to use it you + can specify the following environment variables in your secrets: + + .. code-block:: console + + JAYPORE_EMAIL_ADDR=email-account@gmail.com + JAYPORE_EMAIL_PASSWORD=some-app-password + JAYPORE_EMAIL_TO=myself@gmail.com,mailing-list@gmail.com + JAYPORE_EMAIL_FROM=noreply@gmail.com + + If you're using something other than gmail, you can specify + `JAYPORE_EMAIL_HOST` and `JAYPORE_EMAIL_PORT` as well. + + Once that is done you can supply this remote to your pipeline instead of + the usual gitea one. + + .. code-block:: python + + from jaypore_ci import jci, remotes, repos + + git = repos.Git.from_env() + email = remotes.Email.from_env(repo=git) + with jci.Pipeline(repo=git, remote=email) as p: + pass + # Do something + + """ + + @classmethod + def from_env(cls, *, repo: Repo) -> "Email": + """ + Creates a remote instance from the environment. + """ + remote = urlparse(repo.remote) + owner = Path(remote.path).parts[1] + name = Path(remote.path).parts[2].replace(".git", "") + return cls( + host=os.environ.get("JAYPORE_EMAIL_HOST", "smtp.gmail.com"), + port=int(os.environ.get("JAYPORE_EMAIL_PORT", 465)), + addr=os.environ["JAYPORE_EMAIL_ADDR"], + password=os.environ["JAYPORE_EMAIL_PASSWORD"], + email_to=os.environ["JAYPORE_EMAIL_TO"], + email_from=os.environ["JAYPORE_EMAIL_FROM"], + subject=f"JCI [{owner}/{name}] [{repo.branch} {repo.sha[:8]}]", + branch=repo.branch, + sha=repo.sha, + ) + + def __init__( + self, + *, + host: str, + port: int, + addr: str, + password: str, + email_to: str, + email_from: str, + subject: str, + publish_interval: int = 30, + **kwargs, + ): # pylint: disable=too-many-arguments + super().__init__(**kwargs) + # --- customer + self.host = host + self.port = port + self.addr = addr + self.password = password + self.email_to = email_to + self.email_from = email_from + self.subject = subject + self.timeout = 10 + self.publish_interval = publish_interval + # --- + self.__smtp__ = None + self.__last_published_at__ = None + self.__last_report__ = None + + @property + def smtp(self): + if self.__smtp__ is None: + smtp = smtplib.SMTP_SSL(self.host, self.port) + smtp.ehlo() + smtp.login(self.addr, self.password) + self.__smtp__ = smtp + return self.__smtp__ + + def logging(self): + """ + Return's a logging instance with information about gitea bound to it. + """ + return logger.bind(addr=self.addr, host=self.host, port=self.port) + + def publish(self, report: str, status: str) -> None: + """ + Will publish the report via email. + + :param report: Report to write to remote. + :param status: One of ["pending", "success", "error", "failure", + "warning"] This is the dot next to each commit in gitea. + """ + assert status in ("pending", "success", "error", "failure", "warning") + if ( + self.__last_published_at__ is not None + and (time.time() - self.__last_published_at__) < self.publish_interval + ): + return + if self.__last_report__ == report: + return + self.__last_report__ = report + self.__last_published_at__ = time.time() + # Let's send the email + msg = EmailMessage() + msg["Subject"] = self.subject + msg["From"] = Address("JayporeCI", "JayporeCI", self.email_from) + msg["To"] = self.email_to + msg.set_content(report) + msg.add_alternative( + f"<html><body><pre>{html_escape(report)}</pre></body></html>", + subtype="html", + ) + try: + self.smtp.send_message(msg) + except Exception as e: # pylint: disable=broad-except + self.logging().exception(e) + self.__last_published_at__ = time.time() + self.logging().info( + "Report published", + subject=self.subject, + email_from=self.email_from, + email_to=self.email_to, + ) diff --git a/jaypore_ci/remotes/gitea.py b/jaypore_ci/remotes/gitea.py @@ -4,13 +4,12 @@ A gitea remote git host. This is used to report pipeline status to the remote. """ import os -import subprocess from pathlib import Path from urllib.parse import urlparse import requests -from jaypore_ci.interfaces import Remote, RemoteApiFailed +from jaypore_ci.interfaces import Remote, RemoteApiFailed, Repo from jaypore_ci.logging import logger @@ -20,7 +19,7 @@ class Gitea(Remote): # pylint: disable=too-many-instance-attributes """ @classmethod - def from_env(cls): + def from_env(cls, *, repo: Repo) -> "Gitea": """ Creates a remote instance from the environment. It will: @@ -30,36 +29,16 @@ class Gitea(Remote): # pylint: disable=too-many-instance-attributes - Create a new pull request for that branch - Allow posting updates using the gitea token provided """ - remote = ( - subprocess.check_output( - "git remote -v | grep push | awk '{print $2}'", shell=True - ) - .decode() - .strip() - ) - assert "https://" in remote, "Only https remotes supported" - assert ".git" in remote - remote = urlparse(remote) - branch = ( - subprocess.check_output( - r"git branch | grep \* | awk '{print $2}'", shell=True - ) - .decode() - .strip() - ) - os.environ["JAYPORE_COMMIT_BRANCH"] = branch - sha = subprocess.check_output("git rev-parse HEAD", shell=True).decode().strip() - os.environ["JAYPORE_COMMIT_SHA"] = sha - owner = Path(remote.path).parts[1] - repo = Path(remote.path).parts[2].replace(".git", "") - token = os.environ["JAYPORE_GITEA_TOKEN"] + os.environ["JAYPORE_COMMIT_BRANCH"] = repo.branch + os.environ["JAYPORE_COMMIT_SHA"] = repo.sha + remote = urlparse(repo.remote) return cls( root=f"{remote.scheme}://{remote.netloc}", - owner=owner, - repo=repo, - branch=branch, - token=token, - sha=sha, + owner=Path(remote.path).parts[1], + repo=Path(remote.path).parts[2].replace(".git", ""), + branch=repo.branch, + token=os.environ["JAYPORE_GITEA_TOKEN"], + sha=repo.sha, ) def __init__( diff --git a/jaypore_ci/remotes/github.py b/jaypore_ci/remotes/github.py @@ -4,13 +4,12 @@ A github remote git host. This is used to report pipeline status to the remote. """ import os -import subprocess from pathlib import Path from urllib.parse import urlparse import requests -from jaypore_ci.interfaces import Remote, RemoteApiFailed +from jaypore_ci.interfaces import Remote, RemoteApiFailed, Repo from jaypore_ci.logging import logger @@ -27,7 +26,7 @@ class Github(Remote): # pylint: disable=too-many-instance-attributes } @classmethod - def from_env(cls): + def from_env(cls, *, repo: Repo) -> "Github": """ Creates a remote instance from the environment. It will: @@ -37,36 +36,16 @@ class Github(Remote): # pylint: disable=too-many-instance-attributes - Create a new pull request for that branch - Allow posting updates using the gitea token provided """ - remote = ( - subprocess.check_output( - "git remote -v | grep push | awk '{print $2}'", shell=True - ) - .decode() - .strip() - ) - assert "https://github.com" in remote, "Only https remotes supported" - assert ".git" in remote - remote = urlparse(remote) - branch = ( - subprocess.check_output( - r"git branch | grep \* | awk '{print $2}'", shell=True - ) - .decode() - .strip() - ) - os.environ["JAYPORE_COMMIT_BRANCH"] = branch - sha = subprocess.check_output("git rev-parse HEAD", shell=True).decode().strip() - os.environ["JAYPORE_COMMIT_SHA"] = sha - owner = Path(remote.path).parts[1] - repo = Path(remote.path).parts[2].replace(".git", "") - token = os.environ["JAYPORE_GITHUB_TOKEN"] + remote = urlparse(repo.remote) + os.environ["JAYPORE_COMMIT_BRANCH"] = repo.branch + os.environ["JAYPORE_COMMIT_SHA"] = repo.sha return cls( root="https://api.github.com", - owner=owner, - repo=repo, - branch=branch, - token=token, - sha=sha, + owner=Path(repo.remote.path).parts[1], + repo=Path(remote.path).parts[2].replace(".git", ""), + branch=repo.branch, + token=os.environ["JAYPORE_GITHUB_TOKEN"], + sha=repo.sha, ) def __init__( diff --git a/jaypore_ci/remotes/mock.py b/jaypore_ci/remotes/mock.py @@ -6,7 +6,7 @@ This is used to report pipeline status to the remote. import os -from jaypore_ci.interfaces import Remote +from jaypore_ci.interfaces import Remote, Repo from jaypore_ci.logging import logger @@ -16,7 +16,7 @@ class Mock(Remote): # pylint: disable=too-many-instance-attributes """ @classmethod - def from_env(cls): + def from_env(cls, *, repo: Repo): return cls(branch=os.environ["JAYPORE_BRANCH"], sha=os.environ["JAYPORE_SHA"]) def logging(self): diff --git a/jaypore_ci/repos/__init__.py b/jaypore_ci/repos/__init__.py @@ -0,0 +1,2 @@ +from .git import Git +from .mock import Mock diff --git a/jaypore_ci/repos/git.py b/jaypore_ci/repos/git.py @@ -0,0 +1,43 @@ +import subprocess +from typing import List + +from jaypore_ci.interfaces import Repo + + +class Git(Repo): + def files_changed(self, target: str) -> List[str]: + "Returns list of files changed between current sha and target" + return ( + subprocess.check_output( + f"git diff --name-only {target} {self.sha}", shell=True + ) + .decode() + .strip() + .split("\n") + ) + + @classmethod + def from_env(cls) -> "Git": + """ + Gets repo status from the environment and git repo on disk. + """ + remote = ( + subprocess.check_output( + "git remote -v | grep push | grep https | awk '{print $2}'", shell=True + ) + .decode() + .strip() + ) + assert "https://" in remote, "Only https remotes supported" + assert ".git" in remote + # NOTE: Later on perhaps we should support non-https remotes as well + # since JCI does not actually do anything with the remote. + branch = ( + subprocess.check_output( + r"git branch | grep \* | awk '{print $2}'", shell=True + ) + .decode() + .strip() + ) + sha = subprocess.check_output("git rev-parse HEAD", shell=True).decode().strip() + return Repo(sha=sha, branch=branch, remote=remote) diff --git a/jaypore_ci/repos/mock.py b/jaypore_ci/repos/mock.py @@ -0,0 +1,20 @@ +from typing import List + +from jaypore_ci.interfaces import Repo + + +class Mock(Repo): + def __init__(self, *, files_changed, **kwargs): + super().__init__(**kwargs) + self.files_changed = files_changed + + def files_changed(self, target: str) -> List[str]: + "Returns list of files changed between current sha and target" + return self.files_changed + + @classmethod + def from_env(cls, **kwargs) -> "Mock": + """ + Gets repo status from the environment and git repo on disk. + """ + return cls(**kwargs) diff --git a/pyproject.toml b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "jaypore_ci" -version = "0.2.9" +version = "0.2.10" description = "" authors = ["arjoonn sharma <arjoonn.94@gmail.com>"] diff --git a/secrets/bin/edit_env.sh b/secrets/bin/edit_env.sh @@ -10,17 +10,19 @@ main (){ KEY_FILE=$(echo "$SECRETS/$NAME.key") ENC_FILE=$(echo "$SECRETS/$NAME.enc") PLAINTEXT_FILE=$(echo "$SECRETS/$NAME.plaintext") - echo $BIN - echo $SECRETS - echo $KEY_FILE - echo $ENC_FILE - echo $PLAINTEXT_FILE + export SOPS_AGE_KEY_FILE=$KEY_FILE + echo "BIN = $BIN" + echo "SECRETS = $SECRETS" + echo "KEY = $KEY_FILE" + echo "SOPS KEY = $SOPS_AGE_KEY_FILE" + echo "ENC = $ENC_FILE" + echo "PLAIN = $PLAINTEXT_FILE" PATH="$BIN:$PATH" - if [[ -f "$SECRETS/$NAME.enc" ]]; then - SOPS_AGE_KEY_FILE=$KEY_FILE sops --decrypt --input-type dotenv --output-type dotenv $ENC_FILE > $PLAINTEXT_FILE + if [[ -f "$ENC_FILE" ]]; then + sops --decrypt --input-type dotenv --output-type dotenv "$ENC_FILE" > "$PLAINTEXT_FILE" fi - vim $PLAINTEXT_FILE - sops --input-type dotenv --output-type dotenv --encrypt --age $(age-keygen -y $KEY_FILE) $PLAINTEXT_FILE > $ENC_FILE - rm $PLAINTEXT_FILE + vim "$PLAINTEXT_FILE" + sops --input-type dotenv --output-type dotenv --encrypt --age $(age-keygen -y "$KEY_FILE") "$PLAINTEXT_FILE" > "$ENC_FILE" + rm "$PLAINTEXT_FILE" } (main $1) diff --git a/secrets/ci.enc b/secrets/ci.enc @@ -1,12 +1,16 @@ -JAYPORE_DOCKER_USER=ENC[AES256_GCM,data:bWzVvBoSwg==,iv:YZWgrVzgYAkV0UtBmMQOpcDPXCGl+RTFXGeN4+FaT8U=,tag:snItFIFsT6j/rsqJjs9UTg==,type:str] -JAYPORE_DOCKER_PWD=ENC[AES256_GCM,data:w+228n0Lu0jF6mdbo2Tl98ata8Mh7EAUiyPcDqnicpZYjGTY,iv:140Ah/i5uI9y9ZpurQFuJ0EAOSz5U2NcMT2eRZnLoI0=,tag:FTqPc+zy+aspiRKAubeGWQ==,type:str] -JAYPORE_NETLIFY_TOKEN=ENC[AES256_GCM,data:sdbG9uX53iNee1i3OVS5x5dehfDB3zvwRv1C9ziUTOCqhG4pxcb3D7+jWg==,iv:XiS/FrUAskxO8P3kpgMCn4R2OXAqOb/JuZMNnHIa2SI=,tag:Plo5Tm6mP/734EYS2JlLqQ==,type:str] -JAYPORE_NETLIFY_SITEID=ENC[AES256_GCM,data:K70Mup4c95eU3oCdY1yZwBNl2pN33RJA1SEnYbZ1NLarE/N6,iv:434UV0BG39eUwuI8TmciO6wNQbmnNZx4VtY/C8iL6R4=,tag:3+s7RIDHpogczYg7VmBanQ==,type:str] -JAYPORE_PYPI_TOKEN=ENC[AES256_GCM,data:TgiQOevajULMgnj0RwqaZ54NUbWDOOc/w9lHY3qc50cyZ+p3sIuUjmNQ+xM+aCLh68OQotRb7bzr+A999/cy1EcIdcsBJJvU4qjHGvFJ4YxsP+dgC0QeXerXqdLxCClOZ9NQ368ffKo4Htxj1mkZE2guXvYUhqR32YQ83MgKRWY8WgCtLm1xnstAAZnAOm+CHKN5jPb1jhXWXuhZk8dsGUCBDyKvQbwCFYw0DhByjQ+NSkLB1K/+s8iEla6UzBaA5AgGF0RvBSSegEhj14BACcA=,iv:UUrkczg4//aNRUSH+ZDwNwEDY8vMO5xGOHH+dXt3Suc=,tag:pIxet3/a1F5gOMDHg9ma9Q==,type:str] -JAYPORE_GITEA_TOKEN=ENC[AES256_GCM,data:hya5PF/zQbkhplmiYHBYbpWQaDxavPs15aHalftvyeuBe5BGKdFtew==,iv:za+/uLXPLD200MTcqDlBdO7aJnDw7eAF123p9yvpeK4=,tag:AUaUcSCgE/YiuUHGVHoH/w==,type:str] +JAYPORE_DOCKER_USER=ENC[AES256_GCM,data:oC92MBM2hA==,iv:1GAxCOZZv0XsiX3PY3vYHnG9J8mcCwJDgNsPdvvRKFQ=,tag:v8AywYAOzvjRtWqlDBeM0g==,type:str] +JAYPORE_DOCKER_PWD=ENC[AES256_GCM,data:CYOp9N93tnQIK+8CmMld/8loFErRvaP9BIaneUTDmgiAutpv,iv:ZIKzJaUogjGcbvophgeNw8CZvTDIBHuVaHq3ichAb9U=,tag:Sg0M8f4Nb7xWdMgwm27e7Q==,type:str] +JAYPORE_NETLIFY_TOKEN=ENC[AES256_GCM,data:6UpWZ0ZWLKraLs1tP3wz7JbAA7nqu+afOtigAQv7FaYS8aTuM3/ozieayQ==,iv:AJXlNpKYHcg8Nh1fAop58MBmV0s/gmjxazuXtWLQqyE=,tag:91hPgDWAWYuTmnZ+Coo2Og==,type:str] +JAYPORE_NETLIFY_SITEID=ENC[AES256_GCM,data:z4QaJ4xUM490WViYv4Acau//pkjgWCQ7kNfW3g4qASw6qLR8,iv:IPhuWvBWZxtYiL5XtQFEnR6RxOiezvc2hISdgLaBzp8=,tag:Yb8jMn2n6HK4ybqgsZNR9Q==,type:str] +JAYPORE_PYPI_TOKEN=ENC[AES256_GCM,data:FPk9CBTboAi2H0afgWtV59IHLOC8xx3WxhN/gIzTcC+lSlXsCyyM1Zio63aWlfufBk4FJcJhpFopVVpijRGzHCpHgC/hYyUKwBO6wenNktM9Ul8LRkQJk7KVvn5cLjAS6yK/vutr3/SHXkzgef3B7EL1J8jjRjDpQQvadDyLPOf20eduWnpwp5fQa6y/KdwhDW36Z6GEzNLbUeevGeeIaidYTVTPacbIEcKh04OUGc5URro8oFv7cBzmR6srxFyqrHNJZtG1ZhZwuPVPneLj+ck=,iv:ujxVdymiIHlUCQEByVVuQqKmCvWA0P7hnxQ73vKnknk=,tag:ih1WgEOTcQmeIxtd80XFYg==,type:str] +JAYPORE_GITEA_TOKEN=ENC[AES256_GCM,data:qQuApRUUZ5GygKg13HoddtlQY20+0yrptaCtUYHMFEl793tXvA+4XQ==,iv:QwHIUQ+5OpGxzCf8aA9bq1hLtUNaKZdAU/5TWPSmlCU=,tag:LUTvMn1EZybQUHscCIKAew==,type:str] +JAYPORE_EMAIL_ADDR=ENC[AES256_GCM,data:xkTmiw==,iv:m8XmWl41mv10DMwc7d3oOWMOxnQf2/6VtK5T2Gws49U=,tag:sJeT0maZP61rphdpa0VOng==,type:str] +JAYPORE_EMAIL_PASSWORD=ENC[AES256_GCM,data:eyPc/Q==,iv:uj6drBxAobrtKgF9VXDJdWCMTX57YOu5WFW7wQIWzFk=,tag:3uAljYsvCQhZ7/FZQQHjXw==,type:str] +JAYPORE_EMAIL_TO=ENC[AES256_GCM,data:yEy5Lw==,iv:mij/qk1P/q/VzWSSE7NW5RGsO7Fpgxof0A0pm4aqYmQ=,tag:clf/3fybvqFTGZj/fOCrSA==,type:str] +JAYPORE_EMAIL_FROM=ENC[AES256_GCM,data:YSoI7Q==,iv:6celjipnC9rNPKAb++hpY4x1TEW1ul/jnUtP6m8EyFE=,tag:6MvYSoPkCerpRhn+jSsd0Q==,type:str] sops_version=3.7.3 -sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzVCtDNkZKVDRmSUVrdlR4\nczEyK1ViVlhpYzJydkR6R2FZY0JYVC9qUlQwCm41S1dkbWRWMFFFWDhKcmcwRTZI\nTFMvUzJ0RXNOYWtHYlNlSHJHVnd1RjQKLS0tIDBGUDNDSnFsak9mMm1SNnpuREY5\nSGRJVVI4ZW91SkdHYUNQeXVJUU9lS1kKvs9P+gI6AN8GWwe2opau/F4d9noxd2mm\nhlQySflV7qKMhKXdt+cct4/atsBLB9BKvcaHBx2BSYBjNA6MwGIirw==\n-----END AGE ENCRYPTED FILE-----\n -sops_mac=ENC[AES256_GCM,data:xErAenO+DaTNk/W4OOqIVNafdVmLMzVDOKb3GRzEjZBuLNNUkNBRVVAyQmkr6+9IeqINnpp+285wg9h8Qf5BPAfSScI5i8Kj/GiqHmiriE//4+bub9BYg+kdP7B3idukGoeJEGUrMBhENUuS7Z5TYgI7UgOlKXSHWWqBgn+MkWo=,iv:25fjibgI3Xd19h+SMQrJbNpJc5QoB+kwfZxo4LXydG0=,tag:lW2GPmTvq8h2Tc84F9Il1A==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxeDRPY1RjanBseFVWcVdW\nZWlFaUk3dnQyUmYzck5iUTc1Z0RvMHZyamtnCnU0aUI0cHo3a2FuRERyV0pkc05x\nWnBseURYK0xFcWxnTklmRzdoQ3BoencKLS0tIFhMUk44dDhweEpTNnJWNGZqWWRS\nclVEOTZUYllmMVBBdjkvZjVtVkVHcHMKonS5h3nfmAYNwz7zPTd0eA+eC7MffKTC\nXfPDIveZrpFJLz0YDdPiFD2sORFlTZog8bitzhgY1MZwQNqE8iZQjw==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_0__map_recipient=age1u0zd477nnqqausg4vtxl3laxz73t5tgt9qw8enmpeeadcmes3eusw3v3m9 +sops_lastmodified=2023-02-19T07:33:53Z +sops_mac=ENC[AES256_GCM,data:J8cqAfeAMvwA8DvtwiPbCjlUllOdgZUcgExK8HENtXhBBLEmaznI2tfB7tF2CSMLg+y9lLL8pmqQQRMr6JMCEbSSetRb0yvV2T1r9gpe5fZ5dd65gD8i1OXeAivqeeaRf8H7cGrb7wnWLn564fq26J6JR8LKahjj6+rzpawXzig=,iv:TDDm8g0bSCNSHWH+brQPUSvC1QHsl/gQ6l1JnVftxXo=,tag:eRRUTYH1+XIB/GinN7HWvQ==,type:str] sops_unencrypted_suffix=_unencrypted -sops_lastmodified=2023-02-02T14:21:37Z diff --git a/tests/conftest.py b/tests/conftest.py @@ -1,17 +1,20 @@ import pytest -from jaypore_ci import jci, executors, remotes, reporters +from jaypore_ci import jci, executors, remotes, reporters, repos @pytest.fixture( scope="function", params=[reporters.Text, reporters.Mock, reporters.Markdown] ) def pipeline(request): + repo = repos.Mock.from_env( + files_changed=[], branch="test_branch", sha="fake_sha", remote="fake_remote" + ) executor = executors.Mock() - remote = remotes.Mock(branch="test_branch", sha="fake") + remote = remotes.Mock(branch=repo.branch, sha=repo.sha) reporter = request.param() p = jci.Pipeline( - executor=executor, remote=remote, reporter=reporter, poll_interval=0 + repo=repo, executor=executor, remote=remote, reporter=reporter, poll_interval=0 ) p.render_report = lambda: "" yield p