Jaypore CI

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

commit b8d8ece3f7014296a30a39266a11a84f5e997be4
parent f38e8fc63b17a02456c5ec4bb55ab41283285783
Author: arjoonn <arjoonn@noreply.localhost>
Date:   Mon, 16 Jan 2023 08:58:36 +0000

path_fix (!23)

Branch auto created by JayporeCI

```jayporeci
╔ 🟢 : JayporeCI       [sha 49f90a0483]
┏━ Docker
┃
┃ 🟢 : JciEnv          [1f1a354a]
┃ 🟢 : Jci             [124d562f]
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Jobs
┃
┃ 🟢 : pytest          [53569f47]
┃ 🟢 : pylint          [ca3755c2]
┃ 🟢 : black           [7d2c2955]
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Publish
┃
┃ 🟢 : DockerHubJcienv [05bd16ed]
┃ 🟢 : PublishPypi     [d652ea7e]
┃ 🟢 : DockerHubJci    [f5220056]
┃ 🟢 : PublishDocs     [4e1ce715]
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
```

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

Diffstat:
MREADME.md | 12++----------
Mcicd/build_and_publish_docs.sh | 2++
Mcicd/pre-push.sh | 12+-----------
Mdocs/.gitignore | 1+
Ddocs/source/getting_started.rst | 44--------------------------------------------
Mdocs/source/index.rst | 48+++++++++++++++++++++++++++++++++++++++++++++---
Ddocs/source/reference/jaypore_ci.rst | 53-----------------------------------------------------
Ddocs/source/reference/modules.rst | 7-------
Mjaypore_ci/executors/docker.py | 35++++++++++++++++++-----------------
Mjaypore_ci/interfaces.py | 26+++++++++++++++++---------
Mjaypore_ci/jci.py | 11++++++++++-
Mjaypore_ci/logging.py | 8++++++--
Mpyproject.toml | 2+-
Asecrets/.gitignore | 3+++
Asecrets/bin/age | 0
Asecrets/bin/age-keygen | 0
Asecrets/bin/edit_env.sh | 25+++++++++++++++++++++++++
Asecrets/bin/set_env.sh | 6++++++
Asecrets/bin/sops | 0
Asecrets/ci.enc | 11+++++++++++
Dsecrets/edit_env.sh | 38--------------------------------------
Dsecrets/jaypore_ci.enc | 11-----------
22 files changed, 148 insertions(+), 207 deletions(-)

diff --git a/README.md b/README.md @@ -4,13 +4,5 @@ Documentation is at : https://www.jayporeci.in ## Usage -```bash -# Install once -curl https://get.jayporeci.in | bash -git add -Av -git commit -m 'Add Jaypore CI' -git push origin - -# Trigger CI whenver you push -git push origin -``` +- Install : `curl https://get.jayporeci.in | bash` +- Trigger : `git push origin` diff --git a/cicd/build_and_publish_docs.sh b/cicd/build_and_publish_docs.sh @@ -6,9 +6,11 @@ set -o pipefail build() { echo "Building docs" + sphinx-apidoc -o docs/source/reference ./jaypore_ci sphinx-build docs/source/ docs/build (cd docs/build && zip -r ../../website.zip ./) } + publish() { echo "Publishing docs" curl -H "Content-Type: application/zip" \ diff --git a/cicd/pre-push.sh b/cicd/pre-push.sh @@ -5,18 +5,8 @@ set -o nounset set -o pipefail -editenv() { - NAME=$1 - SOPS_AGE_KEY_FILE=secrets/$NAME.age sops --decrypt --input-type dotenv --output-type dotenv secrets/$NAME.enc > secrets/$NAME.env - vim secrets/$NAME.env - sops --encrypt --age $(age-keygen -y secrets/$NAME.age) secrets/$NAME.env > secrets/$NAME.enc - rm secrets/$NAME.env -} - run() { - export SECRETS_PATH=secrets - export SECRETS_FILENAME=jaypore_ci - export $(SOPS_AGE_KEY_FILE=/jaypore_ci/repo/$SECRETS_PATH/$SECRETS_FILENAME.age sops --decrypt --input-type dotenv --output-type dotenv /jaypore_ci/repo/$SECRETS_PATH/$SECRETS_FILENAME.enc | xargs) + source /jaypore_ci/repo/secrets/bin/set_env.sh ci cp -r /jaypore_ci/repo/. /jaypore_ci/run cd /jaypore_ci/run/ git clean -fdx diff --git a/docs/.gitignore b/docs/.gitignore @@ -1 +1,2 @@ build/ +reference/ diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst @@ -1,44 +0,0 @@ -Getting Started -=============== - -Installation ------------- - - -To use **Jaypore CI**, first install it using a bash script. - -.. code-block:: console - - $ curl https://get.jayporeci.in | bash - - -Doing this will: - -1. Create a directory called `cicd` in the root of your repo. -2. Create a file `cicd/pre-push.sh` -3. Create a file `cicd/cicd.py` -4. Update your repo's pre-push git hook so that it runs the `cicd/pre-push.sh` file when you push. - - -Basic config ------------- - -Your entire config is inside `cicd/cicd.py`. Edit it to whatever you like! A basic config would look like this: - -.. code-block:: python - - from jaypore_ci import jci - - with jci.Pipeline(image='mydocker/image') as p: - p.job("Black", "black --check .") - p.job("Pylint", "pylint mycode/ tests/") - p.job("PyTest", "pytest tests/") - - -After you make these changes you can `git add -Av` and `git commit -m 'added Jaypore CI'`. - -When you do a `git push origin`, that's when the CI system will get triggered and will run the CI. - -This config will run three jobs in parallel, using the `mydocker/image` docker image. - -See :doc:`examples` for more complex examples and :doc:`ideas` for understanding how it works. diff --git a/docs/source/index.rst b/docs/source/index.rst @@ -24,19 +24,61 @@ For example, here's a CI pipeline for a project. with jci.Pipeline(image='mydockerhub/env_image') as p: p.job("Black", "black --check .") p.job("Pylint", "pylint mycode/ tests/") + p.job("PyTest", "pytest tests/", depends_on=["Black", "Pylint"]) + + +Getting Started +=============== + +Installation +------------ + + +To use **Jaypore CI**, first install it using a bash script. + +.. code-block:: console + + $ curl https://get.jayporeci.in | bash + + +Doing this will: + +1. Create a directory called `cicd` in the root of your repo. +2. Create a file `cicd/pre-push.sh` +3. Create a file `cicd/cicd.py` +4. Update your repo's pre-push git hook so that it runs the `cicd/pre-push.sh` file when you push. + + +Basic config +------------ + +Your entire config is inside `cicd/cicd.py`. Edit it to whatever you like! A basic config would look like this: + +.. code-block:: python + + from jaypore_ci import jci + + with jci.Pipeline(image='mydocker/image') as p: + p.job("Black", "black --check .") + p.job("Pylint", "pylint mycode/ tests/") p.job("PyTest", "pytest tests/") -Go through the :doc:`getting_started` doc to set up your first instance. +After you make these changes you can `git add -Av` and `git commit -m 'added Jaypore CI'`. + +When you do a `git push origin`, that's when the CI system will get triggered and will run the CI. + +This config will run three jobs in parallel, using the `mydocker/image` docker image. + +See :doc:`examples` for more complex examples and :doc:`ideas` for understanding how it works. Contents ---------------- +-------- .. toctree:: :glob: - getting_started ideas examples reference/modules.rst diff --git a/docs/source/reference/jaypore_ci.rst b/docs/source/reference/jaypore_ci.rst @@ -1,53 +0,0 @@ -jaypore\_ci package -=================== - -Submodules ----------- - -jaypore\_ci.docker module -------------------------- - -.. automodule:: jaypore_ci.docker - :members: - :undoc-members: - :show-inheritance: - -jaypore\_ci.gitea module ------------------------- - -.. automodule:: jaypore_ci.gitea - :members: - :undoc-members: - :show-inheritance: - -jaypore\_ci.interfaces module ------------------------------ - -.. automodule:: jaypore_ci.interfaces - :members: - :undoc-members: - :show-inheritance: - -jaypore\_ci.jci module ----------------------- - -.. automodule:: jaypore_ci.jci - :members: - :undoc-members: - :show-inheritance: - -jaypore\_ci.logging module --------------------------- - -.. automodule:: jaypore_ci.logging - :members: - :undoc-members: - :show-inheritance: - -Module contents ---------------- - -.. automodule:: jaypore_ci - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/reference/modules.rst b/docs/source/reference/modules.rst @@ -1,7 +0,0 @@ -jaypore_ci -========== - -.. toctree:: - :maxdepth: 4 - - jaypore_ci diff --git a/jaypore_ci/executors/docker.py b/jaypore_ci/executors/docker.py @@ -9,6 +9,18 @@ from jaypore_ci.interfaces import Executor, TriggerFailed from jaypore_ci.logging import logger +def __check_output__(cmd): + """ + Common arguments that need to be provided while + calling subprocess.check_output + """ + return ( + subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT) + .decode() + .strip() + ) + + class Docker(Executor): """ Run jobs via docker. @@ -19,17 +31,6 @@ class Docker(Executor): - Clean up all jobs when the pipeline exits. """ - def __check_output__(self, cmd): - """ - Common arguments that need to be provided while - calling subprocess.check_output - """ - return ( - subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT) - .decode() - .strip() - ) - def __init__(self): super().__init__() self.pipe_id = None @@ -88,7 +89,7 @@ class Docker(Executor): return net_ls self.logging().info( "Create network", - subprocess=self.__check_output__( + subprocess=__check_output__( f"docker network create -d bridge {self.get_net()}" ), ) @@ -107,7 +108,7 @@ class Docker(Executor): if job.run_id is not None and not job.run_id.startswith("pyrun_"): self.logging().info( "Stop job:", - subprocess=self.__check_output__(f"docker stop -t 1 {job.run_id}"), + subprocess=__check_output__(f"docker stop -t 1 {job.run_id}"), ) job.check_job(with_update_report=False) if job is not None: @@ -121,7 +122,7 @@ class Docker(Executor): assert self.pipe_id is not None, "Cannot delete network if pipe is not set" self.logging().info( "Delete network", - subprocess=self.__check_output__( + subprocess=__check_output__( f"docker network rm {self.get_net()} || echo 'No such net'" ), ) @@ -175,15 +176,15 @@ class Docker(Executor): """ Given a run_id, it will get the status for that run. """ - ps_out = self.__check_output__(f"docker ps -f 'id={run_id}' --no-trunc") + ps_out = __check_output__(f"docker ps -f 'id={run_id}' --no-trunc") is_running = run_id in ps_out # --- exit code - exit_code = self.__check_output__( + exit_code = __check_output__( f"docker inspect {run_id}" " --format='{{.State.ExitCode}}'" ) exit_code = int(exit_code) # --- logs - logs = self.__check_output__(f"docker logs {run_id}") + logs = __check_output__(f"docker logs {run_id}") self.logging().debug( "Check status", run_id=run_id, diff --git a/jaypore_ci/interfaces.py b/jaypore_ci/interfaces.py @@ -12,7 +12,7 @@ class TriggerFailed(Exception): class Status(Enum): - "Each pipeline can be in any one of these statuses" + "Each pipeline can ONLY be in any one of these statuses" PENDING = 10 RUNNING = 30 FAILED = 40 @@ -23,11 +23,11 @@ class Status(Enum): class Executor: """ + An executor is something used to run a job. It could be docker / podman / shell etc. - Something that allows us to run a job. - Must define `__enter__` and `__exit__` so that it can be used as a context - manager. + It must define `__enter__` and `__exit__` so that it can be used as a context manager. + """ def run(self, job: "Job") -> str: @@ -38,7 +38,7 @@ class Executor: self.pipe_id = None self.pipeline = None - def set_pipeline(self, pipeline): + def set_pipeline(self, pipeline: "Pipeline") -> None: """Set the current pipeline to the given one.""" self.pipe_id = id(pipeline) self.pipeline = pipeline @@ -47,13 +47,15 @@ class Executor: return self def __exit__(self, exc_type, exc_value, traceback): - pass + """ + On exit the executor must clean up any pending / stuck / zombie jobs that are still there. + """ class Remote: """ + Something that allows us to show other people the status of the CI job. It could be gitea / github / gitlab / email system. - Something that allows us to post the status of the CI. Must define `__enter__` and `__exit__` so that it can be used as a context manager. @@ -77,15 +79,21 @@ class Remote: @classmethod def from_env(cls): + """ + This function should create a Remote instance from the given environment. + It can read git information / look at environment variables etc. + """ raise NotImplementedError() class Reporter: """ - Something that allows us to report the status of a pipeline + Something that generates the status of a pipeline. + + It can be used to generate reports in markdown, plaintext, html, pdf etc. """ - def render(self, pipeline): + def render(self, pipeline: "Pipeline") -> str: """ Render a report for the pipeline. """ diff --git a/jaypore_ci/jci.py b/jaypore_ci/jci.py @@ -22,6 +22,7 @@ __all__ = ["Pipeline", "Job"] # All of these statuses are considered "finished" statuses FIN_STATUSES = (Status.FAILED, Status.PASSED, Status.TIMEOUT, Status.SKIPPED) +PREFIX = "JAYPORE_" class Job: # pylint: disable=too-many-instance-attributes @@ -157,7 +158,15 @@ class Job: # pylint: disable=too-many-instance-attributes Gets the environment variables for a given job by interpolating it with the pipeline's environment. """ - return {**os.environ, **self.pipeline.pipe_kwargs.get("env", {}), **self.env} + return { + **{ + k[len(PREFIX) :]: v + for k, v in os.environ.items() + if k.startswith(PREFIX) + }, + **self.pipeline.pipe_kwargs.get("env", {}), + **self.env, + } class Pipeline: # pylint: disable=too-many-instance-attributes diff --git a/jaypore_ci/logging.py b/jaypore_ci/logging.py @@ -1,7 +1,5 @@ """ The basic logging module. - -All logs are collected and posted along with the report in the remote PR. """ import logging from typing import Any @@ -14,6 +12,12 @@ jaypore_logs = [] class JayporeLogger: + """ + This is mainly used to collect logs into a single global variable so that + the logs of the CI runner itself can also be posted as part of the CI + report. + """ + def __getstate__(self) -> str: return "stdout" diff --git a/pyproject.toml b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "jaypore_ci" -version = "0.1.6" +version = "0.1.8" description = "" authors = ["arjoonn sharma <arjoonn.94@gmail.com>"] diff --git a/secrets/.gitignore b/secrets/.gitignore @@ -0,0 +1,3 @@ +*.key +*.plaintext +*.env diff --git a/secrets/bin/age b/secrets/bin/age Binary files differ. diff --git a/secrets/bin/age-keygen b/secrets/bin/age-keygen Binary files differ. diff --git a/secrets/bin/edit_env.sh b/secrets/bin/edit_env.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +set -o errexit +set -o pipefail + +main (){ + NAME=$1 + BIN=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + SECRETS=$(echo "$BIN/..") + 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 + if [[ -f "$SECRETS/$NAME.enc" ]]; then + SOPS_AGE_KEY_FILE=$KEY_FILE $BIN/sops --decrypt --input-type dotenv --output-type dotenv $ENC_FILE > $PLAINTEXT_FILE + fi + vim $PLAINTEXT_FILE + $BIN/sops --input-type dotenv --output-type dotenv --encrypt --age $($BIN/age-keygen -y $KEY_FILE) $PLAINTEXT_FILE > $ENC_FILE + rm $PLAINTEXT_FILE +} +(main $1) diff --git a/secrets/bin/set_env.sh b/secrets/bin/set_env.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +BIN=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +SECRETS=$(echo "$BIN/..") +NAME=$1 +export $(SOPS_AGE_KEY_FILE=$SECRETS/$NAME.key $BIN/sops --decrypt --input-type dotenv --output-type dotenv $SECRETS/$NAME.enc | xargs) diff --git a/secrets/bin/sops b/secrets/bin/sops Binary files differ. diff --git a/secrets/ci.enc b/secrets/ci.enc @@ -0,0 +1,11 @@ +JAYPORE_DOCKER_USER=ENC[AES256_GCM,data:EFQJF0xzRQ==,iv:FN1Lk12AMJ3AuZ1lXWR4sxS7vVsdEKoO0eWHn+OFT2Q=,tag:52mEjL0Jqe71QiT/idGAjQ==,type:str] +JAYPORE_DOCKER_PWD=ENC[AES256_GCM,data:93dY+QKRPT67vpPxy+bHvEvLebkr/DjnTN1ILOavEx8pfwyE,iv:YBy3yv+1Nr+HnxwlH4rtOHnk6GEe9sB5ZDXhZIX106I=,tag:Uu1gYvzb08a0C8F6ir/M9Q==,type:str] +JAYPORE_NETLIFY_TOKEN=ENC[AES256_GCM,data:5bPThyqEcEt84m/2DWJKTZhHMMfipFC95XCN7+7ZOyQq9cH0dGHzqP9m0w==,iv:BMuJScVsgLblV3SQ58Ct03gPR5WkEKCSyo2YVfHhMIQ=,tag:hTaFloLUMaJFrirKVWQQTQ==,type:str] +JAYPORE_NETLIFY_SITEID=ENC[AES256_GCM,data:5SO6Ynd3uWtsw61rssbQ0ibGudUc4D76mgIKPyMiMEHYxPTn,iv:npbTOB32me8w9RPj3o6ASuZVi0PhutLz4pPOUyzfr+Y=,tag:ffBx0QZhJQydPaUZW6B79Q==,type:str] +JAYPORE_PYPI_TOKEN=ENC[AES256_GCM,data:IkouQeCYtj/ci3CdHmZ9O+/tmeFtZWbneka/JexhqENdCSUCdpyWGoNppsP6ZNCBYaJMU7seH4vAAlVNBlbJ9aAgZbafHXeAbUb+j5nDuZyxVELSG/GSfoIrIJIH984QTncfrnbZ5tHjOOxl1S+Lh0vxLuMoGUPrKnJsJqhe/n6uJ1+MVoxgQrptuMPwWPGbuQFlF2G0WKZASkGzO89oI3ZclqSXan1qsC0tZMew/hwtsCVQc3w8YHxtBUDDpwbmz/q8HyzWHm8lbfC27KLGHtM=,iv:sHCltgCtsYyvEPmPNOQn+OtI4mtUH1s4wx9HBka+T9I=,tag:9y9QVv1K4vfSQNlEEvcddA==,type:str] +sops_unencrypted_suffix=_unencrypted +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXYnpwdEZVT0Z5TStNR1lh\nUXl2Q2xMZ3M0c0xoV2NBMU5kOXZtRlNNblVZCnNxeXh6b1RXKzBmdVgydkxOWndI\nblJiZW9wUnJCRGwvY0pmNGRWSitOMUUKLS0tIDYzRzlUc1JzeXhZL2dNWjJwZUVP\nTWNoSjRFcUtieXo0Y0lvcTk1bjNxeWsKGuUO8unlGhGgTUP7ySlqYLzrF4mhhhK9\nE0rXRCAPoRxePL+1ND1772THsXBq4RIHk5e1gkOuXt7dzsR4uNdK0A==\n-----END AGE ENCRYPTED FILE-----\n +sops_mac=ENC[AES256_GCM,data:FKVKM90hkkil5Adz8xRjHF4H0hAOSBD62JeoxeQbVMUtATQOHGMvzH6/RcUtoLHOLxveaQkC7ytM7zC90YR/OkSiwCJo1fnBDPE6KHncMfxdB/xnILYqUZiyJe4RbN9EQybbjszE8qwLp5PJU2+ducQV/dS4WRKds4Jsc1jXCjQ=,iv:jg8QYEI2jXY+kDI52kXBsyRVLTBUK2cEKcG2JQxLraA=,tag:3vQ01L4ohNIX5fHqZiRDVA==,type:str] +sops_version=3.7.3 +sops_lastmodified=2023-01-16T07:41:23Z +sops_age__list_0__map_recipient=age1u0zd477nnqqausg4vtxl3laxz73t5tgt9qw8enmpeeadcmes3eusw3v3m9 diff --git a/secrets/edit_env.sh b/secrets/edit_env.sh @@ -1,38 +0,0 @@ -#!/usr/bin/env bash - -set -o errexit -set -o pipefail - -main (){ - NAME=$1 - SOPS_AGE_KEY_FILE=secrets/$NAME.age sops --decrypt --input-type dotenv --output-type dotenv secrets/$NAME.enc > secrets/$NAME.env - vim secrets/$NAME.env - sops --encrypt --age $(age-keygen -y secrets/$NAME.age) secrets/$NAME.env > secrets/$NAME.enc - rm secrets/$NAME.env -} - -help_message (){ - - echo " - Easily edit env files. - Make sure you have age keys available in - - ~/.ssh/smaac/agekeys/<envname>.txt - - If that is available you can run the following to edit env files. - - ./api/bin/edit_env.sh <envname> - - Upon exiting the editor the file will be re-encrypted. - " -} - -if [[ $1 == "--help" || $1 == "-h" ]]; then - help_message - exit 0 -fi -if [ -z $1 ]; then - help_message - exit 0 -fi -(main $1) diff --git a/secrets/jaypore_ci.enc b/secrets/jaypore_ci.enc @@ -1,11 +0,0 @@ -DOCKER_USER=ENC[AES256_GCM,data:2CaivUehNQ==,iv:OX1kGJMcZCPWL7prJOWWXyGdWCeH8PRUgaArQAM8Kp4=,tag:n+a5xU8wKoJ2isWPKl/g7A==,type:str] -DOCKER_PWD=ENC[AES256_GCM,data:WeQDNLJVNDBWitxhUtFCvXSVcn8PVVGjUgGAm8NxOwp85j6m,iv:2SKkjTxI7rAMUfzO2KHBKXDuq+Ud3ofVHZxOc7/6k8s=,tag:Gvg+HvARWPjMymHKg0gYOQ==,type:str] -NETLIFY_TOKEN=ENC[AES256_GCM,data:Th0pfcRKpVVbpELSties8KTpsHbZ8t8u6+XeIgunYcEKPluOxvfMOHglAQ==,iv:X9mfiz/gl9UIT1NBv/nW8VMr/pUK2ZKxjlyktpluyTo=,tag:5ykXSWGKa4Tyox8pxQZQTg==,type:str] -NETLIFY_SITEID=ENC[AES256_GCM,data:mRF8fNJ1OkRguScHQdc6REbb+4wBYEA0Plm42FNqsyyGMP2P,iv:PACpEvLBgPEZ+cy9AgHteeKXLLSM8svAM532guKGkGc=,tag:n24uq0hPVdrsDBQi0Qz58Q==,type:str] -PYPI_TOKEN=ENC[AES256_GCM,data:Xs11z/CmeZublROuasDem9brzTVIH0lIvzcBzXVt/qWyuon0jAwin37R4ZlFGkaFWDuMOlgk0imZsNsUzPxviLI7DB7da/lFekHIqdRt/zHvvsSIE3e+lISUTN53iZRSzQNs1XL07Bot/OcyENnusY2TOJkFdpeg27vC31w2dnBQOfiW9zdn6m+OLRDXfhJ1es0P7T1J5Csb5jZEsDU5pTfZZbGfrO9K5JTf+Z3xVEkn9H5FGUifO6dW+VsW9Mi3hmtZBmc8UeXadIaV8o0GxxE=,iv:ezGLQtkEm/IiY77jV78aZoB7Sr2zK5ZIxSgkmM+ISts=,tag:zLggPvtH3uS5bmf5fZty1A==,type:str] -sops_version=3.7.3 -sops_age__list_0__map_recipient=age1u0zd477nnqqausg4vtxl3laxz73t5tgt9qw8enmpeeadcmes3eusw3v3m9 -sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1VW9NdUtUeHgrcjRROUxP\nSCtramJjZXptMVM5ajFXeGVNVld6bXZIRlVBClE3dEw1dWdZQmIvUGZQYk9ITDNj\nUVpMUXFiWXp6cGlXZFA5a1haNHUwQjAKLS0tIFJmVGdRWW1KdWsxeHhSRG00cXB2\nYzBtaFhrZGRaRWx6ZjByU1BsYWhLelUK5xY5pQJivhjEekm/FCp2cy/D3xdXb5q6\nt1ovJcko2++uM1rO8xrcqW5zXTqdbLBW2lWL8imSlkaMiFs7aVA8jA==\n-----END AGE ENCRYPTED FILE-----\n -sops_lastmodified=2022-12-29T08:40:37Z -sops_unencrypted_suffix=_unencrypted -sops_mac=ENC[AES256_GCM,data:v+MUgHHzcWglAxkY7k8GrFAh9Mwjx5Y0jAn/cjxvQOcDgzd73Sl2k1GGh/v6t5Qonv0w+TSpADW76iG9ffmvLQzNvJyJduVsvEaAfyvUYUabnSSGZMVL6ytI6FloyipEoEoyV+9Ac+e+6eQZtceHWOXTxutp59y5/LsS+dZD3qU=,iv:SkxSE/ZROwarze2Im8KoO9anuBipyTYjWDXoGnrd3Nc=,tag:DAf2oT8VtnUo5TGIFSV7hA==,type:str]