commit f8affe4f0a3f0f61615d1e6236fdf973f853f915
parent 3abe3e251c15fb89ba2cbeca688932a42b03bb3d
Author: arjoonn <arjoonn@noreply.localhost>
Date: Tue, 31 Jan 2023 06:54:33 +0000
Add TUI for walking through job logs (!31)
Branch auto created by JayporeCI
```jayporeci
╔ 🟢 : JayporeCI [sha daee6208e0]
┏━ Docker
┃
┃ 🟢 : Jci [53e256c0] 0: 9
┃ 🟢 : JciEnv [0b07d8db] 0: 0
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Jobs
┃
┃ 🟢 : black [d8e61a5a] 0: 0
┃ 🟢 : pylint [6a810f5c] 0:10
┃ 🟢 : pytest [298ca0e5] 0: 2
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Publish
┃
┃ 🟢 : DockerHubJci [e0645215] 0:56
┃ 🟢 : DockerHubJcienv [e921564b] 1:11
┃ 🟢 : PublishDocs [ab3329dd] 0: 7
┃ 🟢 : PublishPypi [a41b730d] 0: 6
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
```
Co-authored-by: arjoonn sharma <arjoonn@midpathsoftware.com>
Reviewed-on: https://gitea.midpathsoftware.com/midpath/jaypore_ci/pulls/31
Diffstat:
7 files changed, 156 insertions(+), 6 deletions(-)
diff --git a/cicd/pre-push.sh b/cicd/pre-push.sh
@@ -31,7 +31,7 @@ hook() {
echo "JayporeCi: "
JAYPORE_GITEA_TOKEN="${JAYPORE_GITEA_TOKEN:-$TOKEN}" docker run \
-d \
- --name jaypore_ci_$SHA \
+ --name jayporeci__pipe__$SHA \
-e JAYPORE_GITEA_TOKEN \
-e JAYPORE_CODE_DIR=$JAYPORE_CODE_DIR \
-v /var/run/docker.sock:/var/run/docker.sock \
diff --git a/docs/source/index.rst b/docs/source/index.rst
@@ -85,6 +85,20 @@ This would produce a CI report like::
- `1: 3` is the time taken by the job.
+To see your pipelines on your machine you can run:
+
+.. code-blcok:: bash
+
+ docker run \
+ --rm -it \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ --workdir /app \
+ bash -c 'python3 -m jaypore_ci'
+
+
+This will open up a console where you can interact and explore the job logs.
+
+
Examples
========
diff --git a/jaypore_ci/__main__.py b/jaypore_ci/__main__.py
@@ -0,0 +1,3 @@
+from jaypore_ci.tui import Console
+
+Console().run()
diff --git a/jaypore_ci/executors/docker.py b/jaypore_ci/executors/docker.py
@@ -54,7 +54,9 @@ class Docker(Executor):
if self.pipe_id is not None:
self.delete_network()
self.delete_all_jobs()
- self.pipe_id = id(pipeline)
+ self.pipe_id = __check_output__(
+ "cat /proc/self/cgroup | grep name= | awk -F/ '{print $3}'"
+ )
self.pipeline = pipeline
self.create_network()
@@ -66,7 +68,7 @@ class Docker(Executor):
"""
Return a network name based on what the curent pipeline is.
"""
- return f"jaypore_{self.pipe_id}" if self.pipe_id is not None else None
+ return f"jayporeci__net__{self.pipe_id}" if self.pipe_id is not None else None
def create_network(self):
"""
@@ -138,7 +140,7 @@ class Docker(Executor):
for l in job.name.lower().replace(" ", "_")
if l in "abcdefghijklmnopqrstuvwxyz_1234567890"
)
- return f"{self.get_net()}_{name}"
+ return f"jayporeci__job__{self.pipe_id}__{name}"
def run(self, job: "Job") -> str:
"""
@@ -146,7 +148,6 @@ class Docker(Executor):
In case something goes wrong it will raise TriggerFailed
"""
assert self.pipe_id is not None, "Cannot run job if pipe id is not set"
- self.pipe_id = id(job.pipeline) if self.pipe_id is None else self.pipe_id
env_vars = [f"--env {key}={val}" for key, val in job.get_env().items()]
trigger = [
"docker run -d",
diff --git a/jaypore_ci/tui.css b/jaypore_ci/tui.css
@@ -0,0 +1,22 @@
+Screen {
+ background: $surface-darken-1;
+}
+
+#tree-view {
+ display: block;
+ scrollbar-gutter: stable;
+ overflow: auto;
+ width: auto;
+ height: 100%;
+ dock: left;
+}
+
+
+
+#code-view {
+ overflow: auto scroll;
+ min-width: 100%;
+}
+#code {
+ width: auto;
+}
diff --git a/jaypore_ci/tui.py b/jaypore_ci/tui.py
@@ -0,0 +1,110 @@
+import subprocess
+
+from rich.traceback import Traceback
+
+from textual import events
+from textual.app import App, ComposeResult
+from textual.containers import Container, Vertical
+from textual.widgets import Tree, Footer, Header, Static
+
+HELP = """
+- The tree shows commit SHA values for pipelines that have run on this machine.
+- Clicking on the SHA will:
+ - Toggle the list of jobs for that pipeline.
+ - Show pipeline logs
+- Clicking on the job will show logs for that job
+"""
+
+
+def get_pipes_from_docker_ps():
+ lines = (
+ subprocess.check_output(
+ "docker ps -a",
+ shell=True,
+ stderr=subprocess.STDOUT,
+ )
+ .decode()
+ .split("\n")
+ )
+
+ pipes = {}
+ PREFIX = "jayporeci__"
+ for line in lines:
+ if PREFIX not in line:
+ continue
+ kind, *details = line.split(PREFIX)[1].split("__")
+ cid = line.split(" ")[0]
+ if kind == "pipe":
+ if cid not in pipes:
+ pipes[cid] = {"sha": None, "jobs": [], "cid": None}
+ if pipes[cid]["sha"] is None:
+ pipes[cid]["sha"] = details[0][:8]
+ if pipes[cid]["cid"] is None:
+ pipes[cid]["cid"] = cid
+ elif kind == "job":
+ pipe_cid, name = details
+ pipe_cid = pipe_cid[:12]
+ if pipe_cid not in pipes:
+ pipes[pipe_cid] = {"sha": None, "jobs": [], "cid": None}
+ pipes[pipe_cid]["jobs"].append((cid[:12], name))
+ return pipes
+
+
+class Console(App):
+ """Textual CI Job browser app."""
+
+ CSS_PATH = "tui.css"
+ BINDINGS = [
+ ("q", "quit", "Quit"),
+ ]
+
+ def compose(self) -> ComposeResult:
+ """Compose our UI."""
+ yield Header()
+ # Find the job tree
+ tree: Tree[dict] = Tree("JayporeCI", id="tree-view")
+ tree.root.expand()
+ pipes = get_pipes_from_docker_ps()
+ for pipe in pipes.values():
+ pipe_node = tree.root.add(pipe["sha"], data=pipe)
+ for job in pipe["jobs"]:
+ job_cid, job_name = job
+ pipe_node.add_leaf(f"{job_cid[:4]}: {job_name}", data=job)
+ # ---
+ yield Container(
+ tree,
+ Vertical(Static(id="code", expand=True, markup=False), id="code-view"),
+ )
+ yield Footer()
+
+ def on_mount(self, event: events.Mount) -> None: # pylint: disable=unused-argument
+ self.query_one(Tree).show_root = False
+ self.query_one(Tree).focus()
+
+ def on_tree_node_selected(self, event: Tree.NodeSelected) -> None:
+ """Called when the user click a node in the job tree."""
+ event.stop()
+ code_view = self.query_one("#code", Static)
+ data = event.node.data
+ cid = None
+ if isinstance(data, dict) and "cid" in data:
+ cid = name = data["cid"]
+ name = f"Pipeline for SHA: {data['sha']}"
+ elif isinstance(data, tuple):
+ cid, name = data
+ name = f"Job: {name}"
+ if cid is None:
+ code_view.update(HELP)
+ return
+ try:
+ logs = subprocess.check_output(
+ f"docker logs {cid}",
+ shell=True,
+ stderr=subprocess.STDOUT,
+ ).decode()
+ except Exception: # pylint: disable=broad-except
+ code_view.update(Traceback(theme="github-dark", width=None))
+ self.sub_title = "ERROR"
+ else:
+ code_view.update(logs)
+ self.sub_title = name
diff --git a/pyproject.toml b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "jaypore_ci"
-version = "0.2"
+version = "0.2.2"
description = ""
authors = ["arjoonn sharma <arjoonn.94@gmail.com>"]