Skip to content

Commit 44303ec

Browse files
committed
feat: add explicit dogfood and container install paths (#50)
* feat: add action install sources and container image Signed-off-by: Michael Kantor <6068672+kantorcodes@users.noreply.github.com> * fix: harden action install and docker path Signed-off-by: Michael Kantor <6068672+kantorcodes@users.noreply.github.com> --------- Signed-off-by: Michael Kantor <6068672+kantorcodes@users.noreply.github.com>
1 parent 9a1159d commit 44303ec

9 files changed

Lines changed: 182 additions & 5 deletions

File tree

.dockerignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.git
2+
.github
3+
.pytest_cache
4+
.ruff_cache
5+
.venv
6+
__pycache__
7+
dist
8+
tests
9+
action
10+
*.pyc

.github/workflows/e2e-test.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ jobs:
1919
- uses: ./action
2020
id: scan
2121
with:
22+
install_source: local
2223
plugin_dir: tests/fixtures/good-plugin
2324
min_score: 80
2425

@@ -30,6 +31,7 @@ jobs:
3031
- uses: ./action
3132
id: scan
3233
with:
34+
install_source: local
3335
plugin_dir: tests/fixtures/good-plugin
3436
format: json
3537
output: report.json
@@ -52,6 +54,7 @@ jobs:
5254
- uses: ./action
5355
id: scan
5456
with:
57+
install_source: local
5558
plugin_dir: tests/fixtures/good-plugin
5659
format: sarif
5760
output: report.sarif
@@ -75,6 +78,7 @@ jobs:
7578
id: scan
7679
continue-on-error: true
7780
with:
81+
install_source: local
7882
plugin_dir: tests/fixtures/bad-plugin
7983
min_score: 99
8084
- name: Verify failure
@@ -94,6 +98,7 @@ jobs:
9498
- uses: ./action
9599
id: scan
96100
with:
101+
install_source: local
97102
plugin_dir: tests/fixtures/good-plugin
98103
format: markdown
99104
output: report.md

.github/workflows/publish.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,10 @@ jobs:
163163
uv tool install codex-plugin-scanner==${VERSION}
164164
\`\`\`
165165
166+
\`\`\`bash
167+
docker pull ghcr.io/hashgraph-online/codex-plugin-scanner:${VERSION}
168+
\`\`\`
169+
166170
**Full Changelog**: https://github.com/hashgraph-online/codex-plugin-scanner/compare/${LAST_TAG}...v${VERSION}
167171
EOF
168172
@@ -200,3 +204,48 @@ jobs:
200204
--title "v${VERSION}" \
201205
--notes-file ${{ steps.changelog.outputs.notes_path }} \
202206
"${{ steps.action_bundle.outputs.bundle_path }}#hol-codex-plugin-scanner-action-v${VERSION}.zip"
207+
208+
publish-container:
209+
name: Publish container image
210+
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') || (github.event_name == 'workflow_dispatch' && github.event.inputs.publish_target == 'pypi')
211+
needs: [build, publish-pypi]
212+
runs-on: ubuntu-latest
213+
permissions:
214+
contents: read
215+
packages: write
216+
steps:
217+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
218+
- name: Stamp package version
219+
env:
220+
VERSION: ${{ needs.build.outputs.version }}
221+
run: |
222+
sed -i "1,/^version = /{s/^version = .*/version = \"$VERSION\"/}" pyproject.toml
223+
sed -i "1,/^__version__ = /{s/^__version__ = .*/__version__ = \"$VERSION\"/}" src/codex_plugin_scanner/version.py
224+
- name: Prepare image tags
225+
id: tags
226+
env:
227+
IMAGE_NAME: ghcr.io/${{ github.repository }}
228+
VERSION: ${{ needs.build.outputs.version }}
229+
run: |
230+
{
231+
echo "value<<EOF"
232+
echo "${IMAGE_NAME}:${VERSION}"
233+
echo "${IMAGE_NAME}:latest"
234+
echo "EOF"
235+
} >> "$GITHUB_OUTPUT"
236+
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2
237+
- uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567
238+
with:
239+
registry: ghcr.io
240+
username: ${{ github.actor }}
241+
password: ${{ secrets.GITHUB_TOKEN }}
242+
- uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83
243+
with:
244+
context: .
245+
file: ./Dockerfile
246+
push: true
247+
tags: ${{ steps.tags.outputs.value }}
248+
labels: |
249+
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
250+
org.opencontainers.image.version=${{ needs.build.outputs.version }}
251+
org.opencontainers.image.revision=${{ github.sha }}

Dockerfile

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
FROM python:3.12-slim@sha256:3d5ed973e45820f5ba5e46bd065bd88b3a504ff0724d85980dcd05eab361fcf4
2+
3+
ENV PYTHONDONTWRITEBYTECODE=1 \
4+
PYTHONUNBUFFERED=1 \
5+
PIP_NO_CACHE_DIR=1
6+
7+
WORKDIR /app
8+
9+
COPY pyproject.toml README.md LICENSE /app/
10+
COPY src /app/src
11+
12+
RUN python3 -m pip install --upgrade pip && \
13+
python3 -m pip install /app
14+
15+
RUN groupadd --system scanner && \
16+
useradd --system --gid scanner --create-home --home-dir /home/scanner scanner && \
17+
mkdir -p /workspace && \
18+
chown -R scanner:scanner /workspace /home/scanner
19+
20+
WORKDIR /workspace
21+
22+
USER scanner
23+
24+
ENTRYPOINT ["codex-plugin-scanner"]

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
[![PyPI Downloads](https://img.shields.io/pypi/dm/codex-plugin-scanner)](https://pypistats.org/packages/codex-plugin-scanner)
66
[![CI](https://github.com/hashgraph-online/codex-plugin-scanner/actions/workflows/ci.yml/badge.svg)](https://github.com/hashgraph-online/codex-plugin-scanner/actions/workflows/ci.yml)
77
[![Publish](https://github.com/hashgraph-online/codex-plugin-scanner/actions/workflows/publish.yml/badge.svg)](https://github.com/hashgraph-online/codex-plugin-scanner/actions/workflows/publish.yml)
8+
[![Container Image](https://img.shields.io/badge/ghcr-codex--plugin--scanner-2496ED?logo=docker&logoColor=white)](https://github.com/hashgraph-online/codex-plugin-scanner/pkgs/container/codex-plugin-scanner)
89
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/hashgraph-online/codex-plugin-scanner/badge)](https://scorecard.dev/viewer/?uri=github.com/hashgraph-online/codex-plugin-scanner)
910
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE)
1011
[![GitHub Stars](https://img.shields.io/github/stars/hashgraph-online/codex-plugin-scanner?style=social)](https://github.com/hashgraph-online/codex-plugin-scanner/stargazers)
@@ -75,6 +76,15 @@ You can also run the scanner without a local install:
7576
pipx run codex-plugin-scanner ./my-plugin
7677
```
7778

79+
Container-first environments can use the published image instead:
80+
81+
```bash
82+
docker run --rm \
83+
-v "$PWD:/workspace" \
84+
ghcr.io/hashgraph-online/codex-plugin-scanner:<version> \
85+
scan /workspace --format text
86+
```
87+
7888
## What The Scanner Covers
7989

8090
`codex-plugin-scanner` supports a full quality suite:

action/README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ This repository is the Marketplace-facing wrapper for the scanner action. The ma
1212

1313
The default Marketplace install path uses an exact `codex-plugin-scanner` PyPI release, verifies its PyPI provenance against `hashgraph-online/codex-plugin-scanner`, and only then installs it. After installation, the default `scan`, `lint`, and offline `verify` paths operate on local repository content only. Live network probing and submission automation remain explicit opt-in features.
1414

15+
Advanced distribution paths are available when you need them:
16+
17+
- `install_source: local` is the explicit dogfood path for `uses: ./action` inside the source repo.
18+
- `ghcr.io/hashgraph-online/codex-plugin-scanner` is the container distribution for enterprise runners that prefer a reviewed OCI image over runtime package installation.
19+
1520
## Usage
1621

1722
```yaml
@@ -44,6 +49,7 @@ The default Marketplace install path uses an exact `codex-plugin-scanner` PyPI r
4449
| `cisco_skill_scan` | Cisco skill-scanner mode: `auto`, `on`, `off` | `auto` |
4550
| `cisco_policy` | Cisco policy preset: `permissive`, `balanced`, `strict` | `balanced` |
4651
| `install_cisco` | Install the opt-in Cisco skill-scanner dependency used by this repo | `false` |
52+
| `install_source` | Package install source: `pypi` for the reviewed release path, or `local` for source-repo dogfooding | `pypi` |
4753
| `submission_enabled` | Open submission issues for awesome-list and registry automation when the plugin clears the submission threshold | `false` |
4854
| `submission_score_threshold` | Minimum score required before a submission issue is created | `80` |
4955
| `submission_repos` | Comma-separated GitHub repositories that should receive the submission issue | `hashgraph-online/awesome-codex-plugins` |
@@ -127,6 +133,17 @@ This `plugin_dir: "."` pattern is correct for both single-plugin repositories an
127133
install_cisco: true
128134
```
129135

136+
### Dogfood the source-repo action bundle
137+
138+
Use this only inside `hashgraph-online/codex-plugin-scanner`, where the action can install the adjacent source tree directly.
139+
140+
```yaml
141+
- uses: ./action
142+
with:
143+
plugin_dir: "."
144+
install_source: local
145+
```
146+
130147
### Export registry payload for Codex ecosystem automation
131148

132149
```yaml
@@ -236,3 +253,16 @@ Set `mode` to one of `scan`, `lint`, `verify`, or `submit`.
236253
For `submit` mode, point `plugin_dir` at one concrete plugin directory. Repository-mode discovery is supported for `scan`, `lint`, and `verify`, but `submit` intentionally remains single-plugin.
237254

238255
For `scan` mode, set `upload_sarif: true` to emit and upload SARIF automatically instead of wiring a separate upload step by hand.
256+
257+
## Container Distribution
258+
259+
The scanner is also published as an OCI image for container-first environments:
260+
261+
```bash
262+
docker run --rm \
263+
-v "$PWD:/workspace" \
264+
ghcr.io/hashgraph-online/codex-plugin-scanner:<version> \
265+
scan /workspace --format text
266+
```
267+
268+
The image installs the scanner from the reviewed source tree at release build time. It is separate from the Marketplace action so teams that prefer `docker://` or explicit `docker run` flows can use a pinned image without changing the secure default action path.

action/action.yml

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ inputs:
7171
description: "Install the opt-in Cisco skill-scanner dependency used by this repo"
7272
required: false
7373
default: "false"
74+
install_source:
75+
description: "Package install source: pypi for the reviewed release path, or local for source-repo dogfooding"
76+
required: false
77+
default: "pypi"
7478
submission_enabled:
7579
description: "Open submission issues for awesome-list and registry automation when the plugin clears the submission threshold"
7680
required: false
@@ -167,6 +171,9 @@ runs:
167171

168172
- name: Install scanner
169173
shell: bash
174+
env:
175+
INSTALL_SOURCE: ${{ inputs.install_source }}
176+
INSTALL_CISCO: ${{ inputs.install_cisco }}
170177
run: |
171178
LOCAL_SOURCE=""
172179
for candidate in "$GITHUB_ACTION_PATH" "$GITHUB_ACTION_PATH/.."; do
@@ -176,13 +183,17 @@ runs:
176183
fi
177184
done
178185
179-
if [ -n "$LOCAL_SOURCE" ]; then
180-
if [ "${{ inputs.install_cisco }}" = "true" ]; then
186+
if [ "$INSTALL_SOURCE" = "local" ]; then
187+
if [ -z "$LOCAL_SOURCE" ]; then
188+
echo "install_source=local requires the source repository checkout (for example: uses: ./action inside codex-plugin-scanner)." >&2
189+
exit 1
190+
fi
191+
if [ "$INSTALL_CISCO" = "true" ]; then
181192
python3 -m pip install "$LOCAL_SOURCE[cisco]"
182193
else
183194
python3 -m pip install "$LOCAL_SOURCE"
184195
fi
185-
else
196+
elif [ "$INSTALL_SOURCE" = "pypi" ]; then
186197
SCANNER_VERSION_FILE="$GITHUB_ACTION_PATH/scanner-version.txt"
187198
CISCO_VERSION_FILE="$GITHUB_ACTION_PATH/cisco-version.txt"
188199
PYPI_ATTESTATIONS_VERSION_FILE="$GITHUB_ACTION_PATH/pypi-attestations-version.txt"
@@ -213,9 +224,12 @@ runs:
213224
fi
214225
215226
python3 -m pip install "$DIST_DIR/$DIST_BASENAME"
216-
if [ "${{ inputs.install_cisco }}" = "true" ]; then
227+
if [ "$INSTALL_CISCO" = "true" ]; then
217228
python3 -m pip install "cisco-ai-skill-scanner==${CISCO_VERSION}"
218229
fi
230+
else
231+
echo "Unsupported install_source: $INSTALL_SOURCE" >&2
232+
exit 1
219233
fi
220234
221235
- name: Run scanner

action/scanner-version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.4.0
1+
1.4.8

tests/test_action_bundle.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,15 @@ def test_action_metadata_includes_marketplace_branding_and_fallback_install() ->
1414
assert 'color: "blue"' in action_text
1515
assert "actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405" in action_text
1616
assert 'python3 -m pip install "pypi-attestations==' in action_text
17+
assert "install_source:" in action_text
18+
assert 'default: "pypi"' in action_text
19+
assert "INSTALL_SOURCE: ${{ inputs.install_source }}" in action_text
20+
assert "INSTALL_CISCO: ${{ inputs.install_cisco }}" in action_text
21+
assert 'if [ "$INSTALL_SOURCE" = "local" ]; then' in action_text
22+
assert "install_source=local requires the source repository checkout" in action_text
1723
assert 'python3 -m pip install "$LOCAL_SOURCE"' in action_text
1824
assert 'python3 -m pip install "$LOCAL_SOURCE[cisco]"' in action_text
25+
assert 'elif [ "$INSTALL_SOURCE" = "pypi" ]; then' in action_text
1926
assert (
2027
'python3 -m pip download --only-binary=:all: --no-deps --dest "$DIST_DIR" '
2128
'"codex-plugin-scanner==${SCANNER_VERSION}"'
@@ -76,6 +83,17 @@ def test_publish_workflow_attaches_marketplace_action_bundle() -> None:
7683
assert """printf '%s\\n' "${VERSION}" > "${BUNDLE_ROOT}/scanner-version.txt" """[:-1] in workflow_text
7784
assert 'cp action/cisco-version.txt "${BUNDLE_ROOT}/cisco-version.txt"' in workflow_text
7885
assert 'cp action/pypi-attestations-version.txt "${BUNDLE_ROOT}/pypi-attestations-version.txt"' in workflow_text
86+
assert "docker pull ghcr.io/hashgraph-online/codex-plugin-scanner:${VERSION}" in workflow_text
87+
assert "publish-container:" in workflow_text
88+
assert "packages: write" in workflow_text
89+
assert "docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2" in workflow_text
90+
assert "docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567" in workflow_text
91+
assert "docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83" in workflow_text
92+
assert "ghcr.io/${{ github.repository }}" in workflow_text
93+
assert "${IMAGE_NAME}:latest" in workflow_text
94+
assert "org.opencontainers.image.version=${{ needs.build.outputs.version }}" in workflow_text
95+
e2e_workflow_text = (ROOT / ".github" / "workflows" / "e2e-test.yml").read_text(encoding="utf-8")
96+
assert e2e_workflow_text.count("install_source: local") == 5
7997

8098

8199
def test_ci_workflow_covers_cross_platform_runtime() -> None:
@@ -132,6 +150,9 @@ def test_action_bundle_docs_live_in_action_readme() -> None:
132150
assert "published action bundle" in action_readme
133151
assert "Source of Truth" in action_readme
134152
assert "verifies its PyPI provenance" in action_readme
153+
assert "install_source: local" in action_readme
154+
assert "uses: ./action" in action_readme
155+
assert "ghcr.io/hashgraph-online/codex-plugin-scanner:<version>" in action_readme
135156
assert "online`, `submission_enabled`, and `upload_sarif`" in action_readme
136157
assert "registry_payload_output" in action_readme
137158
assert "grade_label" in action_readme
@@ -152,3 +173,17 @@ def test_readme_uses_stable_apache_license_badge() -> None:
152173
assert "https://img.shields.io/github/license/hashgraph-online/codex-plugin-scanner" not in readme
153174
assert "publish-action-repo.yml" in readme
154175
assert "docs/github-action-marketplace.md" not in readme
176+
assert "ghcr.io/hashgraph-online/codex-plugin-scanner:<version>" in readme
177+
assert "Container Image" in readme
178+
179+
180+
def test_container_files_exist_for_enterprise_distribution() -> None:
181+
dockerfile_text = (ROOT / "Dockerfile").read_text(encoding="utf-8")
182+
dockerignore_text = (ROOT / ".dockerignore").read_text(encoding="utf-8")
183+
184+
assert "FROM python:3.12-slim@sha256:" in dockerfile_text
185+
assert 'ENTRYPOINT ["codex-plugin-scanner"]' in dockerfile_text
186+
assert "python3 -m pip install /app" in dockerfile_text
187+
assert "USER scanner" in dockerfile_text
188+
assert ".git" in dockerignore_text
189+
assert "tests" in dockerignore_text

0 commit comments

Comments
 (0)