cutip-blocks 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. cutip_blocks-0.1.0/.github/workflows/ci.yml +155 -0
  2. cutip_blocks-0.1.0/.github/workflows/integration.yml +38 -0
  3. cutip_blocks-0.1.0/.github/workflows/release.yml +94 -0
  4. cutip_blocks-0.1.0/.gitignore +41 -0
  5. cutip_blocks-0.1.0/CLAUDE.md +57 -0
  6. cutip_blocks-0.1.0/LICENSE +21 -0
  7. cutip_blocks-0.1.0/PKG-INFO +107 -0
  8. cutip_blocks-0.1.0/README.md +79 -0
  9. cutip_blocks-0.1.0/cutip_blocks/__init__.py +6 -0
  10. cutip_blocks-0.1.0/cutip_blocks/blocks/__init__.py +1 -0
  11. cutip_blocks-0.1.0/cutip_blocks/blocks/config.py +53 -0
  12. cutip_blocks-0.1.0/cutip_blocks/blocks/container.py +68 -0
  13. cutip_blocks-0.1.0/cutip_blocks/blocks/crictl.py +46 -0
  14. cutip_blocks-0.1.0/cutip_blocks/blocks/download.py +60 -0
  15. cutip_blocks-0.1.0/cutip_blocks/blocks/file.py +94 -0
  16. cutip_blocks-0.1.0/cutip_blocks/blocks/k8s.py +250 -0
  17. cutip_blocks-0.1.0/cutip_blocks/blocks/service.py +69 -0
  18. cutip_blocks-0.1.0/cutip_blocks/blocks/ssh.py +172 -0
  19. cutip_blocks-0.1.0/cutip_blocks/blocks/validate.py +47 -0
  20. cutip_blocks-0.1.0/cutip_blocks/decorator.py +40 -0
  21. cutip_blocks-0.1.0/cutip_blocks/registry.py +62 -0
  22. cutip_blocks-0.1.0/docs/blocks/config.md +3 -0
  23. cutip_blocks-0.1.0/docs/blocks/container.md +3 -0
  24. cutip_blocks-0.1.0/docs/blocks/crictl.md +3 -0
  25. cutip_blocks-0.1.0/docs/blocks/download.md +3 -0
  26. cutip_blocks-0.1.0/docs/blocks/file.md +3 -0
  27. cutip_blocks-0.1.0/docs/blocks/k8s.md +3 -0
  28. cutip_blocks-0.1.0/docs/blocks/service.md +3 -0
  29. cutip_blocks-0.1.0/docs/blocks/ssh.md +3 -0
  30. cutip_blocks-0.1.0/docs/blocks/validate.md +3 -0
  31. cutip_blocks-0.1.0/docs/capabilities.md +9 -0
  32. cutip_blocks-0.1.0/docs/index.md +46 -0
  33. cutip_blocks-0.1.0/docs/patch-notes.md +11 -0
  34. cutip_blocks-0.1.0/mkdocs.yml +74 -0
  35. cutip_blocks-0.1.0/pyproject.toml +74 -0
  36. cutip_blocks-0.1.0/tests/__init__.py +0 -0
  37. cutip_blocks-0.1.0/tests/test_decorator.py +40 -0
  38. cutip_blocks-0.1.0/tests/test_file_blocks.py +24 -0
  39. cutip_blocks-0.1.0/tests/test_registry.py +39 -0
  40. cutip_blocks-0.1.0/tests/test_ssh.py +44 -0
  41. cutip_blocks-0.1.0/uv.lock +880 -0
@@ -0,0 +1,155 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - "feat/**"
7
+ - "bug/**"
8
+ - "claude/**"
9
+ pull_request:
10
+ types: [opened, synchronize, reopened]
11
+ branches:
12
+ - staging
13
+ - integration
14
+
15
+ jobs:
16
+ # ── Auto-label and assign on PR open ────────────────────────────────────────
17
+ auto-label:
18
+ name: Label & Assign PR
19
+ runs-on: ubuntu-latest
20
+ if: github.event_name == 'pull_request' && github.event.action == 'opened'
21
+
22
+ permissions:
23
+ pull-requests: write
24
+
25
+ steps:
26
+ - name: Apply label and assignee
27
+ env:
28
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
29
+ run: |
30
+ PR="${{ github.event.pull_request.number }}"
31
+ BRANCH="${{ github.head_ref }}"
32
+ AUTHOR="${{ github.event.pull_request.user.login }}"
33
+
34
+ gh pr edit "$PR" --repo "${{ github.repository }}" --add-assignee "$AUTHOR"
35
+
36
+ if echo "$BRANCH" | grep -qE '^feat/'; then
37
+ LABEL="feature"; COLOR="0075ca"
38
+ elif echo "$BRANCH" | grep -qE '^bug/'; then
39
+ LABEL="bugfix"; COLOR="d73a4a"
40
+ elif echo "$BRANCH" | grep -qE '^docs/'; then
41
+ LABEL="documentation"; COLOR="006b75"
42
+ else
43
+ LABEL="feature"; COLOR="0075ca"
44
+ fi
45
+
46
+ gh label create "$LABEL" --color "$COLOR" --force --repo "${{ github.repository }}" 2>/dev/null || true
47
+ gh pr edit "$PR" --repo "${{ github.repository }}" --add-label "$LABEL"
48
+
49
+ # ── Lint ────────────────────────────────────────────────────────────────────
50
+ lint:
51
+ name: Ruff Lint & Format
52
+ runs-on: ubuntu-latest
53
+
54
+ steps:
55
+ - uses: actions/checkout@v4
56
+
57
+ - name: Install uv
58
+ uses: astral-sh/setup-uv@v5
59
+ with:
60
+ version: "latest"
61
+
62
+ - name: Install dependencies
63
+ run: uv sync
64
+
65
+ - name: Ruff check
66
+ run: uv run ruff check .
67
+
68
+ - name: Ruff format check
69
+ run: uv run ruff format --check .
70
+
71
+ # ── Type Check ──────────────────────────────────────────────────────────────
72
+ type-check:
73
+ name: Type Check (ty)
74
+ runs-on: ubuntu-latest
75
+
76
+ steps:
77
+ - uses: actions/checkout@v4
78
+
79
+ - name: Install uv
80
+ uses: astral-sh/setup-uv@v5
81
+ with:
82
+ version: "latest"
83
+
84
+ - name: Install dependencies
85
+ run: uv sync
86
+
87
+ - name: Run ty
88
+ run: uv run ty check cutip_blocks/
89
+
90
+ # ── Unit Tests ──────────────────────────────────────────────────────────────
91
+ unit-tests:
92
+ name: Unit Tests
93
+ runs-on: ubuntu-latest
94
+
95
+ steps:
96
+ - uses: actions/checkout@v4
97
+
98
+ - name: Install uv
99
+ uses: astral-sh/setup-uv@v5
100
+ with:
101
+ version: "latest"
102
+
103
+ - name: Install dependencies
104
+ run: uv sync
105
+
106
+ - name: Run tests
107
+ run: uv run pytest tests/ -v
108
+
109
+ # ── Smoke Test ──────────────────────────────────────────────────────────────
110
+ smoke-test:
111
+ name: Smoke Test
112
+ runs-on: ubuntu-latest
113
+
114
+ steps:
115
+ - uses: actions/checkout@v4
116
+
117
+ - name: Install uv
118
+ uses: astral-sh/setup-uv@v5
119
+ with:
120
+ version: "latest"
121
+
122
+ - name: Build wheel
123
+ run: uv build --wheel
124
+
125
+ - name: Smoke-test import
126
+ run: |
127
+ uv venv .wheel-test
128
+ uv pip install --python .wheel-test/bin/python dist/cutip_blocks-*.whl
129
+ .wheel-test/bin/python -c "
130
+ from cutip_blocks import BlockRegistry
131
+ r = BlockRegistry.discover()
132
+ assert len(r.blocks) >= 30, f'Expected 30+ blocks, got {len(r.blocks)}'
133
+ print(f'OK: {len(r.blocks)} blocks across {len(r.categories())} categories')
134
+ "
135
+
136
+ # ── Docs Build ──────────────────────────────────────────────────────────────
137
+ docs-build:
138
+ name: Docs Build
139
+ runs-on: ubuntu-latest
140
+
141
+ steps:
142
+ - uses: actions/checkout@v4
143
+
144
+ - name: Install uv
145
+ uses: astral-sh/setup-uv@v5
146
+ with:
147
+ version: "latest"
148
+
149
+ - name: Install docs dependencies
150
+ run: |
151
+ uv venv .venv
152
+ uv pip install mkdocs-material "mkdocs<2.0" mkdocs-callouts
153
+
154
+ - name: Build docs (strict)
155
+ run: uv run mkdocs build --strict
@@ -0,0 +1,38 @@
1
+ name: Integration
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - integration
7
+
8
+ jobs:
9
+ prune:
10
+ name: Delete merged branches
11
+ runs-on: ubuntu-latest
12
+
13
+ permissions:
14
+ contents: write
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ with:
19
+ fetch-depth: 0
20
+
21
+ - name: Prune merged remote branches
22
+ run: |
23
+ git fetch --prune origin
24
+
25
+ MERGED=$(git branch -r --merged origin/integration \
26
+ | grep -vE 'origin/(integration|staging|HEAD|release/.+|gh-pages)' \
27
+ | sed 's|^\s*origin/||' \
28
+ | xargs)
29
+
30
+ if [ -z "$MERGED" ]; then
31
+ echo "Nothing to prune."
32
+ exit 0
33
+ fi
34
+
35
+ echo "Pruning: $MERGED"
36
+ for branch in $MERGED; do
37
+ git push origin --delete "$branch" && echo " Deleted: $branch"
38
+ done
@@ -0,0 +1,94 @@
1
+ name: Release
2
+
3
+ on:
4
+ pull_request:
5
+ types: [closed]
6
+ branches:
7
+ - integration
8
+
9
+ jobs:
10
+ test:
11
+ name: Test Suite
12
+ runs-on: ubuntu-latest
13
+ if: >-
14
+ github.event.pull_request.merged == true &&
15
+ startsWith(github.head_ref, 'release/')
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - name: Install uv
21
+ uses: astral-sh/setup-uv@v5
22
+ with:
23
+ version: "latest"
24
+
25
+ - name: Install dependencies
26
+ run: uv sync
27
+
28
+ - name: Lint
29
+ run: uv run ruff check .
30
+
31
+ - name: Unit tests
32
+ run: uv run pytest tests/ -v
33
+
34
+ build:
35
+ name: Build & GitHub Release
36
+ needs: test
37
+ runs-on: ubuntu-latest
38
+
39
+ permissions:
40
+ contents: write
41
+
42
+ steps:
43
+ - uses: actions/checkout@v4
44
+
45
+ - name: Install uv
46
+ uses: astral-sh/setup-uv@v5
47
+ with:
48
+ version: "latest"
49
+
50
+ - name: Extract version
51
+ id: version
52
+ run: |
53
+ VERSION=$(grep 'version' pyproject.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
54
+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
55
+
56
+ - name: Build wheel and sdist
57
+ run: uv build
58
+
59
+ - name: Create GitHub Release
60
+ uses: softprops/action-gh-release@v2
61
+ with:
62
+ tag_name: v${{ steps.version.outputs.version }}
63
+ name: v${{ steps.version.outputs.version }}
64
+ generate_release_notes: true
65
+ files: dist/*
66
+ env:
67
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
68
+
69
+ publish:
70
+ name: Publish to PyPI
71
+ needs: build
72
+ runs-on: ubuntu-latest
73
+ if: vars.PUBLISH_TO_PYPI == 'true'
74
+
75
+ environment:
76
+ name: pypi
77
+ url: https://pypi.org/p/cutip-blocks
78
+
79
+ permissions:
80
+ id-token: write
81
+
82
+ steps:
83
+ - uses: actions/checkout@v4
84
+
85
+ - name: Install uv
86
+ uses: astral-sh/setup-uv@v5
87
+ with:
88
+ version: "latest"
89
+
90
+ - name: Build
91
+ run: uv build
92
+
93
+ - name: Publish to PyPI
94
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,41 @@
1
+ # ── Virtual Environments ──────────────────────────────────────────────────────
2
+ .venv/
3
+ venv/
4
+ env/
5
+
6
+ # ── Python ────────────────────────────────────────────────────────────────────
7
+ __pycache__/
8
+ *.py[cod]
9
+ *.pyo
10
+ *.pyd
11
+ *.so
12
+ *.egg
13
+ *.egg-info/
14
+ dist/
15
+ build/
16
+ .eggs/
17
+
18
+ # ── Environment Files ─────────────────────────────────────────────────────────
19
+ .env
20
+ .env.*
21
+ !.env.example
22
+
23
+ # ── Testing & Coverage ────────────────────────────────────────────────────────
24
+ .pytest_cache/
25
+ .coverage
26
+ coverage.xml
27
+ htmlcov/
28
+
29
+ # ── MkDocs ────────────────────────────────────────────────────────────────────
30
+ site/
31
+
32
+ # ── IDE & Editor ──────────────────────────────────────────────────────────────
33
+ .vscode/
34
+ .idea/
35
+ *.swp
36
+ *.swo
37
+ .DS_Store
38
+
39
+ # ── Claude Code ───────────────────────────────────────────────────────────────
40
+ .claude/settings.local.json
41
+ .claude/worktrees/
@@ -0,0 +1,57 @@
1
+ # CUTIP Blocks — Claude Working Context
2
+
3
+ ## What is CUTIP Blocks
4
+
5
+ Reusable workflow blocks for CUTIP. Each block is a decorated Python function that maps to a single CLI operation (kubectl, ssh, cp, etc.). Blocks execute at runtime and provide metadata for cutip-desktop visualization.
6
+
7
+ ## Key Conventions
8
+
9
+ - **Never auto-commit.** Only commit when explicitly asked.
10
+ - **uv** is the package manager. Always `uv run`, `uv add`.
11
+ - **Run tests**: `uv run pytest tests/ -v`
12
+ - **Block naming**: `category.verb` — `k8s.get_secret`, `ssh.exec`, `file.copy`
13
+ - **Each block function gets only the `@block` decorator** — no stacking
14
+ - **SSH sessions use `sesh` parameter name** — never `conn`, `session`, `ssh`
15
+ - **Logging**: every block logs its command via loguru with `[BlockName]` prefix
16
+ - **Redaction**: passwords/tokens/keys are never logged — use `****`
17
+
18
+ ## Architecture
19
+
20
+ ```
21
+ cutip_blocks/
22
+ ├── __init__.py # Public API: block decorator, BlockRegistry
23
+ ├── decorator.py # @block decorator + BlockMeta dataclass
24
+ ├── registry.py # BlockRegistry — discovers all blocks
25
+ └── blocks/ # Block implementations by category
26
+ ├── container.py # start, stop, remove, exec, exec_stream
27
+ ├── ssh.py # session, exec, probe (SSHSession with redaction)
28
+ ├── file.py # copy, copy_tree, read_yaml, write_yaml, replace, is_empty
29
+ ├── k8s.py # get_deployment, get_secret, get_pod, exec, cp, apply, patch_deployment, rollout_status
30
+ ├── download.py # http_fetch
31
+ ├── config.py # render_template, substitute_vars
32
+ ├── validate.py # path_exists, env_var_set, ip_valid
33
+ ├── service.py # poll_until_ready, wait_for_exit
34
+ └── crictl.py # image_ls, image_rm, image_import
35
+ ```
36
+
37
+ ## Block Categories
38
+
39
+ | Category | Tool | Examples |
40
+ |----------|------|---------|
41
+ | container | podman/docker | start, stop, remove, exec |
42
+ | ssh | ssh/paramiko | session, exec, probe |
43
+ | file | filesystem | copy, replace, is_empty |
44
+ | k8s | kubectl | get_secret, get_pod, cp, apply |
45
+ | crictl | crictl/ctr | image_ls, image_rm, image_import |
46
+ | download | http | http_fetch |
47
+ | config | templates | render_template |
48
+ | validate | checks | path_exists, env_var_set |
49
+ | service | daemons | poll_until_ready |
50
+
51
+ ## Branch Conventions
52
+
53
+ Same as cutip — see cutip/CLAUDE.md.
54
+
55
+ ## Versioning
56
+
57
+ Semantic versioning. Stays at 0.x until cutip-core reaches 1.0.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Joshua Jerome
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,107 @@
1
+ Metadata-Version: 2.4
2
+ Name: cutip-blocks
3
+ Version: 0.1.0
4
+ Summary: Reusable workflow blocks for CUTIP — container, SSH, k8s, file, and more
5
+ Project-URL: Homepage, https://github.com/joshuajerome/cutip-blocks
6
+ Project-URL: Repository, https://github.com/joshuajerome/cutip-blocks
7
+ Project-URL: Bug Tracker, https://github.com/joshuajerome/cutip-blocks/issues
8
+ Author: Joshua Jerome
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: blocks,containers,cutip,kubernetes,ssh,workflow
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Requires-Python: >=3.11
19
+ Requires-Dist: cutip>=0.2.0
20
+ Requires-Dist: loguru>=0.7
21
+ Requires-Dist: paramiko>=3.0
22
+ Requires-Dist: pyyaml>=6.0
23
+ Provides-Extra: docs
24
+ Requires-Dist: mkdocs-callouts>=1.14; extra == 'docs'
25
+ Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
26
+ Requires-Dist: mkdocs<2.0; extra == 'docs'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # cutip-blocks
30
+
31
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/)
32
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
33
+ [![cutip](https://img.shields.io/badge/cutip-%E2%89%A50.2.0-purple)](https://github.com/joshuajerome/cutip)
34
+
35
+ Reusable workflow blocks for [CUTIP](https://github.com/joshuajerome/cutip) — Container Unit Templates in Python.
36
+
37
+ Each block is a decorated Python function that maps to a single CLI operation (`kubectl`, `ssh`, `cp`, etc.). Blocks execute at runtime and provide metadata for [cutip-desktop](https://github.com/joshuajerome/cutip-desktop) DAG visualization.
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ pip install cutip-blocks
43
+ ```
44
+
45
+ ## Block Categories
46
+
47
+ | Category | Blocks | Maps to |
48
+ |----------|--------|---------|
49
+ | **container** | `start`, `stop`, `remove`, `exec`, `exec_stream` | `podman`/`docker` |
50
+ | **ssh** | `session`, `exec`, `probe` | `ssh`/`paramiko` |
51
+ | **file** | `copy`, `copy_tree`, `read_yaml`, `write_yaml`, `read_json`, `write_json`, `replace`, `is_empty` | filesystem |
52
+ | **k8s** | `get_deployment`, `get_secret`, `get_pod`, `exec`, `cp`, `apply`, `patch_deployment`, `rollout_status` | `kubectl` |
53
+ | **crictl** | `image_ls`, `image_rm`, `image_import` | `crictl`/`ctr` |
54
+ | **download** | `http_fetch` | HTTP GET |
55
+ | **config** | `render_template`, `substitute_vars` | template rendering |
56
+ | **validate** | `path_exists`, `env_var_set`, `ip_valid` | precondition checks |
57
+ | **service** | `poll_until_ready`, `wait_for_exit` | readiness polling |
58
+
59
+ ## Usage
60
+
61
+ ```python
62
+ from cutip.workflow import action, orchestrator, stage
63
+ from cutip_blocks.blocks import container, ssh, k8s
64
+
65
+ @action(name="Check Deployment")
66
+ def check_deploy(ctx, sesh, ns, deploy):
67
+ k8s.get_deployment(ctx, sesh, namespace=ns, deployment=deploy)
68
+
69
+ @orchestrator
70
+ def main(ctx):
71
+ container.start(ctx, container="my-app")
72
+
73
+ with ssh.session(ctx, container="my-app",
74
+ host="10.0.0.1", username="root",
75
+ password=ctx.config["password"]) as sesh:
76
+
77
+ stage("Validation")
78
+ check_deploy(ctx, sesh, "default", "web")
79
+
80
+ stage("Operations")
81
+ k8s.apply(ctx, sesh, file="/tmp/patch.yaml")
82
+
83
+ container.stop(ctx, container="my-app")
84
+ ```
85
+
86
+ ## SSH Session
87
+
88
+ All SSH-based blocks share a persistent connection via context manager. One SSH handshake, reused for all commands. Credentials are redacted in all log output.
89
+
90
+ ```
91
+ INFO | [Get Deployment] ssh root@10.0.0.1 :: kubectl get deployment -n default web -o name
92
+ INFO | [Get Secret] ssh root@10.0.0.1 :: kubectl get secret -n ns creds -o name
93
+ ```
94
+
95
+ ## Block Discovery
96
+
97
+ ```python
98
+ from cutip_blocks import BlockRegistry
99
+
100
+ registry = BlockRegistry.discover()
101
+ for meta, fn in registry.blocks:
102
+ print(f"{meta.category}.{meta.action} — {meta.name}")
103
+ ```
104
+
105
+ ## License
106
+
107
+ MIT
@@ -0,0 +1,79 @@
1
+ # cutip-blocks
2
+
3
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/)
4
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
5
+ [![cutip](https://img.shields.io/badge/cutip-%E2%89%A50.2.0-purple)](https://github.com/joshuajerome/cutip)
6
+
7
+ Reusable workflow blocks for [CUTIP](https://github.com/joshuajerome/cutip) — Container Unit Templates in Python.
8
+
9
+ Each block is a decorated Python function that maps to a single CLI operation (`kubectl`, `ssh`, `cp`, etc.). Blocks execute at runtime and provide metadata for [cutip-desktop](https://github.com/joshuajerome/cutip-desktop) DAG visualization.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ pip install cutip-blocks
15
+ ```
16
+
17
+ ## Block Categories
18
+
19
+ | Category | Blocks | Maps to |
20
+ |----------|--------|---------|
21
+ | **container** | `start`, `stop`, `remove`, `exec`, `exec_stream` | `podman`/`docker` |
22
+ | **ssh** | `session`, `exec`, `probe` | `ssh`/`paramiko` |
23
+ | **file** | `copy`, `copy_tree`, `read_yaml`, `write_yaml`, `read_json`, `write_json`, `replace`, `is_empty` | filesystem |
24
+ | **k8s** | `get_deployment`, `get_secret`, `get_pod`, `exec`, `cp`, `apply`, `patch_deployment`, `rollout_status` | `kubectl` |
25
+ | **crictl** | `image_ls`, `image_rm`, `image_import` | `crictl`/`ctr` |
26
+ | **download** | `http_fetch` | HTTP GET |
27
+ | **config** | `render_template`, `substitute_vars` | template rendering |
28
+ | **validate** | `path_exists`, `env_var_set`, `ip_valid` | precondition checks |
29
+ | **service** | `poll_until_ready`, `wait_for_exit` | readiness polling |
30
+
31
+ ## Usage
32
+
33
+ ```python
34
+ from cutip.workflow import action, orchestrator, stage
35
+ from cutip_blocks.blocks import container, ssh, k8s
36
+
37
+ @action(name="Check Deployment")
38
+ def check_deploy(ctx, sesh, ns, deploy):
39
+ k8s.get_deployment(ctx, sesh, namespace=ns, deployment=deploy)
40
+
41
+ @orchestrator
42
+ def main(ctx):
43
+ container.start(ctx, container="my-app")
44
+
45
+ with ssh.session(ctx, container="my-app",
46
+ host="10.0.0.1", username="root",
47
+ password=ctx.config["password"]) as sesh:
48
+
49
+ stage("Validation")
50
+ check_deploy(ctx, sesh, "default", "web")
51
+
52
+ stage("Operations")
53
+ k8s.apply(ctx, sesh, file="/tmp/patch.yaml")
54
+
55
+ container.stop(ctx, container="my-app")
56
+ ```
57
+
58
+ ## SSH Session
59
+
60
+ All SSH-based blocks share a persistent connection via context manager. One SSH handshake, reused for all commands. Credentials are redacted in all log output.
61
+
62
+ ```
63
+ INFO | [Get Deployment] ssh root@10.0.0.1 :: kubectl get deployment -n default web -o name
64
+ INFO | [Get Secret] ssh root@10.0.0.1 :: kubectl get secret -n ns creds -o name
65
+ ```
66
+
67
+ ## Block Discovery
68
+
69
+ ```python
70
+ from cutip_blocks import BlockRegistry
71
+
72
+ registry = BlockRegistry.discover()
73
+ for meta, fn in registry.blocks:
74
+ print(f"{meta.category}.{meta.action} — {meta.name}")
75
+ ```
76
+
77
+ ## License
78
+
79
+ MIT
@@ -0,0 +1,6 @@
1
+ """CUTIP Blocks — reusable workflow blocks for CUTIP."""
2
+
3
+ from cutip_blocks.decorator import BlockMeta, block
4
+ from cutip_blocks.registry import BlockRegistry
5
+
6
+ __all__ = ["block", "BlockMeta", "BlockRegistry"]
@@ -0,0 +1 @@
1
+ """Block implementations by category."""
@@ -0,0 +1,53 @@
1
+ """Config blocks — template rendering and variable substitution."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from pathlib import Path
7
+
8
+ from loguru import logger
9
+
10
+ from cutip_blocks.decorator import block
11
+
12
+
13
+ @block(name="Render Template", category="config", action="render_template")
14
+ def render_template(
15
+ ctx,
16
+ *,
17
+ template: str | Path,
18
+ dest: str | Path,
19
+ variables: dict[str, str],
20
+ ) -> Path:
21
+ """Render a template file with {{ var }} substitution.
22
+
23
+ Args:
24
+ template: Path to template file.
25
+ dest: Path to write rendered output.
26
+ variables: Dict of variable name → value.
27
+ """
28
+ template, dest = Path(template), Path(dest)
29
+ logger.info("[Render Template] {} → {} ({} vars)", template, dest, len(variables))
30
+
31
+ text = template.read_text(encoding="utf-8")
32
+ for key, value in variables.items():
33
+ text = text.replace(f"{{{{ {key} }}}}", str(value))
34
+ text = text.replace(f"{{{{{key}}}}}", str(value))
35
+
36
+ dest.parent.mkdir(parents=True, exist_ok=True)
37
+ dest.write_text(text, encoding="utf-8")
38
+ return dest
39
+
40
+
41
+ @block(name="Substitute Vars", category="config", action="substitute_vars")
42
+ def substitute_vars(ctx, *, text: str, variables: dict[str, str]) -> str:
43
+ """Replace {{ var }} placeholders in a string.
44
+
45
+ Args:
46
+ text: Input string with placeholders.
47
+ variables: Dict of variable name → value.
48
+ """
49
+ result = text
50
+ for key, value in variables.items():
51
+ result = result.replace(f"{{{{ {key} }}}}", str(value))
52
+ result = result.replace(f"{{{{{key}}}}}", str(value))
53
+ return result