Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions .github/workflows/release-pypi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
name: Release to PyPI

# Publishing is deliberately split from `Build & test`, which triggers on bare `push:` and so
# already fires on a tag -- but nothing gates on it, so a tag push would otherwise publish in
# parallel with the tests rather than after them. Here `publish` needs `build` needs `test`.
#
# Authentication is Trusted Publishing (OIDC): PyPI verifies a short-lived token bound to this
# repository, this workflow filename and the named environment. There is no API token to store,
# rotate, or leak -- which is why the `id-token: write` permission is granted to the publish job
# alone and nowhere else.

on:
push:
# Bare semver, because that is what every tag since 1.0.0-beta.2 uses. The two `v`-prefixed
# tags are from 2022 and abandoned. The trailing `*` catches prereleases like 1.3.0-beta.1.
tags: ["[0-9]+.[0-9]+.[0-9]+*"]
Comment on lines +14 to +16
workflow_dispatch:
inputs:
target:
description: "Where to publish. Rehearse on TestPyPI before tagging for real."
type: choice
options: [testpypi, pypi]
default: testpypi

permissions:
contents: read

Check warning on line 26 in .github/workflows/release-pypi.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Move this read permission from workflow level to job level.

See more on https://sonarcloud.io/project/issues?id=StackGuardian_policy-framework&issues=AaAzgP-a_t0cXg66CQhj&open=AaAzgP-a_t0cXg66CQhj&pullRequest=284

jobs:
guard:
name: Check the tag matches the shipped version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
# A PyPI version is immutable: a wrong one can be yanked but never replaced, and the fix is
# always a new number. Nothing else in this repository asserts that the tag, setup.py and
# `tirith --version` agree, so it is asserted here, before anything is built.
- name: Tag == tirith.__version__
if: startsWith(github.ref, 'refs/tags/')
run: |
set -euo pipefail
shipped=$(python -c "import re;print(re.search(r'^__version__ = \"([^\"]+)\"', open('src/tirith/__init__.py').read(), re.M).group(1))")
tag="${GITHUB_REF_NAME}"
echo "tag=$tag shipped=$shipped"
if [ "$tag" != "$shipped" ]; then
echo "::error::tag $tag does not match src/tirith/__init__.py ($shipped)"
exit 1
fi

test:
name: Test before publishing
needs: guard
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: Pipfile.lock
- run: pip install . && pip install pipenv && pipenv install --system -d

Check warning on line 66 in .github/workflows/release-pypi.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--only-binary :all:" can lead to the execution of setup scripts. Make sure it is safe here.

See more on https://sonarcloud.io/project/issues?id=StackGuardian_policy-framework&issues=AaAzgP-a_t0cXg66CQhk&open=AaAzgP-a_t0cXg66CQhk&pullRequest=284

Check warning on line 66 in .github/workflows/release-pypi.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--only-binary :all:" can lead to the execution of setup scripts. Make sure it is safe here.

See more on https://sonarcloud.io/project/issues?id=StackGuardian_policy-framework&issues=AaAzgP-a_t0cXg66CQhm&open=AaAzgP-a_t0cXg66CQhm&pullRequest=284

Check warning on line 66 in .github/workflows/release-pypi.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using dependencies without locking resolved versions is security-sensitive.

See more on https://sonarcloud.io/project/issues?id=StackGuardian_policy-framework&issues=AaAzgP-a_t0cXg66CQhl&open=AaAzgP-a_t0cXg66CQhl&pullRequest=284

Check warning on line 66 in .github/workflows/release-pypi.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using dependencies without locking resolved versions is security-sensitive.

See more on https://sonarcloud.io/project/issues?id=StackGuardian_policy-framework&issues=AaAzgP-a_t0cXg66CQhn&open=AaAzgP-a_t0cXg66CQhn&pullRequest=284

Check warning on line 66 in .github/workflows/release-pypi.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using dependencies without locking resolved versions is security-sensitive.

See more on https://sonarcloud.io/project/issues?id=StackGuardian_policy-framework&issues=AaAzgP-a_t0cXg66CQho&open=AaAzgP-a_t0cXg66CQho&pullRequest=284
- run: pytest

build:
name: Build sdist and wheel
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install build twine

Check warning on line 78 in .github/workflows/release-pypi.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--only-binary :all:" can lead to the execution of setup scripts. Make sure it is safe here.

See more on https://sonarcloud.io/project/issues?id=StackGuardian_policy-framework&issues=AaAzgP-a_t0cXg66CQhp&open=AaAzgP-a_t0cXg66CQhp&pullRequest=284

Check warning on line 78 in .github/workflows/release-pypi.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using dependencies without locking resolved versions is security-sensitive.

See more on https://sonarcloud.io/project/issues?id=StackGuardian_policy-framework&issues=AaAzgP-a_t0cXg66CQhq&open=AaAzgP-a_t0cXg66CQhq&pullRequest=284
- run: python -m build
# `twine check` validates that the README renders as a PyPI description. Catching that here
# rather than after upload matters, because the description cannot be corrected in place.
- run: twine check dist/*
# The TUI ships a stylesheet and five bundled examples as package data. A wheel missing them
# still installs and still starts -- it renders unstyled with an empty playground, which is a
# worse failure than not starting, and one no import check would catch.
- name: The wheel carries its data files
run: |
set -euo pipefail
python - <<'PY'
import glob, sys, zipfile
names = zipfile.ZipFile(glob.glob("dist/*.whl")[0]).namelist()
css = [n for n in names if n.endswith("tui/app.css")]
examples = [n for n in names if "/tui/examples/" in n and n.endswith(("about.md", "policy.json", "input.json"))]
print(f"stylesheet={css} example files={len(examples)}")
if not css or len(examples) < 3:
sys.exit("wheel is missing TUI package data")
Comment on lines +92 to +96
PY
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/

publish:
name: Publish
needs: build
runs-on: ubuntu-latest
environment: ${{ github.event_name == 'workflow_dispatch' && inputs.target || 'pypi' }}
permissions:
id-token: write # the OIDC token PyPI verifies; nothing else needs it
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
# Two steps rather than one with a computed `repository-url`: the action defaults that input
# to the real PyPI endpoint, and passing an empty string to mean "use the default" is not a
# contract it documents.
- name: Publish to TestPyPI
if: github.event_name == 'workflow_dispatch' && inputs.target == 'testpypi'
uses: pypa/gh-action-pypi-publish@release/v1

Check failure on line 120 in .github/workflows/release-pypi.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use full commit SHA hash for this dependency.

See more on https://sonarcloud.io/project/issues?id=StackGuardian_policy-framework&issues=AaAzgP-a_t0cXg66CQhr&open=AaAzgP-a_t0cXg66CQhr&pullRequest=284
with:
repository-url: https://test.pypi.org/legacy/

- name: Publish to PyPI
if: github.event_name != 'workflow_dispatch' || inputs.target == 'pypi'
uses: pypa/gh-action-pypi-publish@release/v1

Check failure on line 126 in .github/workflows/release-pypi.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use full commit SHA hash for this dependency.

See more on https://sonarcloud.io/project/issues?id=StackGuardian_policy-framework&issues=AaAzgP-a_t0cXg66CQhs&open=AaAzgP-a_t0cXg66CQhs&pullRequest=284
18 changes: 16 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0



## [Unreleased]

## [1.3.0] - 2026-08-24

### Added
- `tirith ui`: an interactive interface with three tabs.
Expand All @@ -24,7 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Playground** — edit a policy and an input side by side and watch the verdict move, with
five worked examples that mostly fail on purpose and explain why.
- `--serve` runs the same interface over HTTP for a browser.
- Optional extra: `pip install 'py-tirith[tui]'`. Not a hard dependency — the interface needs
- Optional extra: `pip install 'tirith-iac-governance[tui]'`. Not a hard dependency — the interface needs
Python 3.9 while tirith supports 3.8, and using tirith as a CI gate should stay
dependency-light. Without it, `tirith ui` prints how to install it and exits 1.
- A policy validator behind the interface, reporting the mistakes that are otherwise silent:
Expand All @@ -36,6 +37,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
`platform`, so `--json` output remains byte-identical to the golden file.
- No new runtime dependencies for anyone who does not install the extra.

### Packaging
- **Published to PyPI as `tirith-iac-governance`.** `pip install tirith-iac-governance`,
or `pip install 'tirith-iac-governance[tui]'` for the interface. Previously the only way in
was a git URL, which needed git and the network at build time and could not resolve wheels.
The import and the command are still `tirith`; `pip install tirith` remains an unrelated
project that has held that name since 2016.
- The version is now read from `src/tirith/__init__.py` alone, rather than being duplicated in
`setup.py` and kept in step by hand.
- `pyproject.toml` declares a `[build-system]`, so builds no longer depend on whatever
setuptools happens to be present.
- Metadata corrected: SPDX `Apache-2.0` with an explicit `license_files`, Python 3.10-3.12
classifiers to match the tested matrix, and `Development Status :: 5 - Production/Stable`.

## [1.2.0] - 2026-08-03

### Added
Expand Down
25 changes: 6 additions & 19 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,28 +1,15 @@
include *.lock
include *.md
include LICENSE
include Pipfile
recursive-include ci *.gitkeep
recursive-include cli *.sh
recursive-include policies *.json
recursive-include resources *.JPG
recursive-include resources *.conf
recursive-include resources *.css
recursive-include resources *.dockerignore
recursive-include resources *.ico
recursive-include resources *.js
recursive-include resources *.json
recursive-include resources *.md
recursive-include resources *.svg
recursive-include resources Makefile

# The TUI's stylesheet, and the about.md plus policy JSON beside each bundled playground example.
# Without these the installed interface loads unstyled and the playground has nothing to open.
recursive-include src *.css
recursive-include src *.json
recursive-include src *.md
# The TUI's stylesheet, and the about.md beside each bundled playground example. Without
# these the installed interface loads unstyled and the playground has nothing to open.
recursive-include src *.css
recursive-include src *.new
recursive-include src *.old
recursive-include src *.py
recursive-include src *.rego

recursive-include tests *.json

global-exclude *.py[cod] __pycache__/* *.so *.dylib
40 changes: 20 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md)
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://raw.githubusercontent.com/StackGuardian/tirith/main/LICENSE)
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](https://raw.githubusercontent.com/StackGuardian/tirith/main/CODE_OF_CONDUCT.md)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=StackGuardian_policy-framework&metric=alert_status&token=4a4d06e73940505edb7fc9d27a7f03b35fbbf23d)](https://sonarcloud.io/summary/new_code?id=StackGuardian_policy-framework)
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=StackGuardian_policy-framework&metric=sqale_rating&token=4a4d06e73940505edb7fc9d27a7f03b35fbbf23d)](https://sonarcloud.io/summary/new_code?id=StackGuardian_policy-framework)
Expand All @@ -13,7 +13,7 @@
>
> Explore a failing evaluation down to the resource that caused it, assemble policies from a
> form, and experiment in a playground with worked examples. Try it with
> `pip install 'py-tirith[tui] @ git+https://github.com/StackGuardian/tirith.git'`, then
> `pip install 'tirith-iac-governance[tui]'`, then
> `tirith ui` — see
> [The interactive interface](#the-interactive-interface).
>
Expand Down Expand Up @@ -104,18 +104,18 @@ failed, and on which resource and value.
### For users

```
pip install git+https://github.com/StackGuardian/tirith.git
pip install tirith-iac-governance
```

Pin a tag rather than tracking the default branch, so a CI job cannot change behaviour underneath you:
Pin the version in CI, so a release cannot change behaviour underneath you:

```
pip install "git+https://github.com/StackGuardian/tirith.git@1.0.5"
pip install "tirith-iac-governance==1.3.0"
```

`1.0.5` is the newest tag; `git ls-remote --tags https://github.com/StackGuardian/tirith.git` lists
them. Tirith is not on PyPI — `pip install tirith` installs an unrelated project of the same name, so
install from git. Python 3.8 or newer.
The distribution is **`tirith-iac-governance`**; the import and the command are both `tirith`. Note that
`pip install tirith` installs an unrelated project of the same name — that name has belonged to
someone else on PyPI since 2016. Python 3.8 or newer.

### For developers

Expand Down Expand Up @@ -178,7 +178,7 @@ pip install -e .

```
tirith --version
tirith 1.2.0
tirith 1.3.0
```

Congratulations! Tirith has been setup in your system
Expand Down Expand Up @@ -236,11 +236,11 @@ nobody gating a pipeline should pay to install an interface they never open. It
3.9 or newer, while tirith itself still supports 3.8:

```bash
pip install 'py-tirith[tui] @ git+https://github.com/StackGuardian/tirith.git'
pip install 'tirith-iac-governance[tui]'
```

Tirith is not on PyPI — `pip install py-tirith` finds nothing and `pip install tirith` installs an
unrelated project of the same name — so the extra is requested against the git URL.
The interface is an extra rather than a dependency, so a CI gate does not pay to install a UI it
never opens.

```bash
tirith ui # playground, with worked examples
Expand Down Expand Up @@ -374,7 +374,7 @@ policy:
image: python:3.12
needs: [plan]
script:
- pip install "git+https://github.com/StackGuardian/tirith.git@1.0.5"
- pip install "tirith-iac-governance==1.3.0"
- tirith -policy-path .tirith/policies -input-path plan.json --fail-on-error
```

Expand Down Expand Up @@ -442,7 +442,7 @@ Common flags:
| `--output-json` / `--output-markdown` | Write the verdict to files for a later CI step |

`--api-url` overrides `--region` for a self-hosted or dedicated host. Every flag is in
[docs/platform-check.md](docs/platform-check.md) or `tirith platform check --help`.
[docs/platform-check.md](https://raw.githubusercontent.com/StackGuardian/tirith/main/docs/platform-check.md) or `tirith platform check --help`.

Running this from GitHub Actions? Use [the action](#github-actions) instead — it wires up the plan
discovery, the sticky pull-request comment, the check run and the exit codes for you.
Expand Down Expand Up @@ -650,7 +650,7 @@ Input:


Output:
![](docs/tf_plan_example.gif)
![](https://raw.githubusercontent.com/StackGuardian/tirith/main/docs/tf_plan_example.gif)

JSON Output:
```json
Expand Down Expand Up @@ -899,7 +899,7 @@ Input:
```

Output:
![](docs/infracost_example.gif)
![](https://raw.githubusercontent.com/StackGuardian/tirith/main/docs/infracost_example.gif)

JSON Output:
```json
Expand Down Expand Up @@ -1044,7 +1044,7 @@ Example Input:
```

Output:
![](docs/sg_workflow_example.gif)
![](https://raw.githubusercontent.com/StackGuardian/tirith/main/docs/sg_workflow_example.gif)


JSON Output:
Expand Down Expand Up @@ -1202,7 +1202,7 @@ Example Input
```

Output:
![](docs/json_example.gif)
![](https://raw.githubusercontent.com/StackGuardian/tirith/main/docs/json_example.gif)

JSON Output
```json
Expand Down Expand Up @@ -1482,7 +1482,7 @@ Final expression used:

We are calling for contributors to help build out new features, review pull requests, fix bugs, and
maintain overall code quality. Email us at team[at]stackguardian.io, or get started by reading
[contributing.md](./CONTRIBUTING.md).
[contributing.md](https://raw.githubusercontent.com/StackGuardian/tirith/main/CONTRIBUTING.md).

### Getting an issue assigned

Expand Down
2 changes: 1 addition & 1 deletion documentation/docs/getting-started-with-tirith.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import TabItem from '@theme/TabItem';

Explore a failing evaluation down to the resource that caused it, assemble policies from a form, and
experiment in a playground with worked examples. Install it with
`pip install 'py-tirith[tui] @ git+https://github.com/StackGuardian/tirith.git'` and run
`pip install 'tirith-iac-governance[tui]'` and run
`tirith ui` — see
[the interactive interface](tirith-usage/interactive-interface.md).

Expand Down
5 changes: 2 additions & 3 deletions documentation/docs/tirith-installation/quick-intallation.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,14 @@ If you simply want to install and start using Tirith, this option provides a fas

## Prerequisite
- Make sure your machine has [Python](https://www.python.org/downloads/) and [pip](https://pip.pypa.io/en/stable/installation/) installed.
- Install [Git](https://git-scm.com/downloads) on your machine.

## Steps to Install Tirith

### Step 1: Install using the `pip` command
Run the following command in your terminal to download Tirith directly from the GitHub repository and install it on your local system. This command ensures that you have the latest version.
Run the following command in your terminal to install Tirith from PyPI. This gives you the latest release.

```bash
pip install git+https://github.com/StackGuardian/tirith.git
pip install tirith-iac-governance
```


Expand Down
13 changes: 6 additions & 7 deletions documentation/docs/tirith-usage/ci-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,13 @@ policy:
image: python:3.12
needs: [plan]
script:
- pip install "git+https://github.com/StackGuardian/tirith.git@1.0.5"
- pip install "tirith-iac-governance==1.3.0"
- tirith -policy-path .tirith/policies -input-path plan.json --fail-on-error
```

Tirith is **not on PyPI** — `pip install tirith` installs an unrelated project of the same name.
Install from git, and pin a tag rather than tracking the default branch so a CI job cannot change
behaviour underneath you. `1.0.5` is the newest tag;
`git ls-remote --tags https://github.com/StackGuardian/tirith.git` lists them. Python 3.8 or newer.
The distribution is **`tirith-iac-governance`**; the import and the command are both `tirith`. Pin the
version in CI so a release cannot change behaviour underneath you. Note that `pip install tirith`
installs an unrelated project of the same name. Python 3.8 or newer.

To evaluate your organization's policies instead of the committed files, swap the last line for
`tirith platform check` and supply credentials as CI variables:
Expand All @@ -126,7 +125,7 @@ policy:
variables:
SG_ORG: my-org # SG_API_TOKEN comes from a masked CI/CD variable
script:
- pip install "git+https://github.com/StackGuardian/tirith.git@1.0.5"
- pip install "tirith-iac-governance==1.3.0"
- tirith platform check --workflow-id my-repo --input-path plan.json --fail-on-error
```

Expand All @@ -138,7 +137,7 @@ Nothing above is GitLab-specific: any runner that can execute a container and pr
the same way. The recipe is always the same three steps —

1. produce the input document (`terraform show -json tfplan > plan.json`);
2. `pip install "git+https://github.com/StackGuardian/tirith.git@1.0.5"`;
2. `pip install "tirith-iac-governance==1.3.0"`;
3. `tirith -policy-path <policies> -input-path plan.json --fail-on-error`

— and gate the job on the exit code, which every CI system does by default for a non-zero exit.
Expand Down
Loading
Loading