Skip to content

ci: add PR test, scheduled runs, and compliance check workflows #4

ci: add PR test, scheduled runs, and compliance check workflows

ci: add PR test, scheduled runs, and compliance check workflows #4

name: Example Compliance Check
# Verifies that new example folders added in a PR follow repo conventions.
# Only checks folders that are new (not previously on the base branch).
# Rules enforced:
# 1. run_examples.sh must exist and contain set -e
# 2. OPIK_PROJECT_NAME must be set — either exported in run_examples.sh (scripts)
# or referenced in a .py file (use-cases/guides with a config module)
# 3. README.md must exist
# 4. pyproject.toml must exist; requirements.txt must not
# 5. If litellm is a dependency, OPIK_EXAMPLES_MODEL must be referenced in a .py file
#
# This check is secrets-free so it runs safely on PRs from forks.
on:
pull_request:
paths:
- "examples/**"
- "integrations/**"
- "scripts/**"
- "use-cases/**"
- "guides/**"
permissions:
contents: read
jobs:
compliance:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Find changed example folders
id: changed-folders
run: |
changed_files=$(git diff --name-only "origin/${{ github.base_ref }}...HEAD")
folders=""
while IFS= read -r file; do
top=$(echo "$file" | cut -d'/' -f1)
case "$top" in
integrations)
parent=$(echo "$file" | cut -d'/' -f1,2,3)
;;
examples|scripts|use-cases|guides)
parent=$(echo "$file" | cut -d'/' -f1,2)
;;
*)
continue
;;
esac
folders="$folders $parent"
done <<< "$changed_files"
# Deduplicate and trim
folders=$(echo "$folders" | tr ' ' '\n' | sort -u | grep -v '^$' | tr '\n' ' ' | xargs)
echo "folders=$folders" >> "$GITHUB_OUTPUT"
if [[ -z "$folders" ]]; then
echo "No example folders changed."
else
echo "Changed example folders: $folders"
fi
- name: Check compliance
if: steps.changed-folders.outputs.folders != ''
run: |
FAILED=0
for folder in ${{ steps.changed-folders.outputs.folders }}; do
# Skip if the folder was deleted entirely
if [[ ! -d "$folder" ]]; then
echo "Skipping deleted folder: $folder"
continue
fi
# Only enforce on NEW folders (not yet on the base branch).
# Modifying an existing example does not require adding run_examples.sh.
if git ls-tree "origin/${{ github.base_ref }}" "$folder" 2>/dev/null | grep -q .; then
echo " Skipping '$folder' (existing folder — compliance enforced on new examples only)"
continue
fi
echo ""
echo "Checking: $folder"
if [[ ! -f "$folder/run_examples.sh" ]]; then
echo "::error file=$folder/run_examples.sh::$folder is missing run_examples.sh"
FAILED=1
else
echo " OK run_examples.sh"
fi
if [[ ! -f "$folder/README.md" ]]; then
echo "::error file=$folder/README.md::$folder is missing README.md"
FAILED=1
else
echo " OK README.md"
fi
if [[ ! -f "$folder/pyproject.toml" ]]; then
echo "::error::$folder is missing pyproject.toml (uv projects only — no requirements.txt)"
FAILED=1
else
echo " OK pyproject.toml"
fi
if [[ -f "$folder/requirements.txt" ]]; then
echo "::error file=$folder/requirements.txt::$folder has requirements.txt — declare deps in pyproject.toml instead"
FAILED=1
fi
if [[ -f "$folder/run_examples.sh" ]]; then
if ! grep -q "set -e" "$folder/run_examples.sh"; then
echo "::error file=$folder/run_examples.sh::$folder/run_examples.sh must contain 'set -e'"
FAILED=1
else
echo " OK set -e in run_examples.sh"
fi
# OPIK_PROJECT_NAME may be exported in run_examples.sh (scripts)
# or defined as a constant in a .py file (use-cases/guides with config.py).
if grep -q "export OPIK_PROJECT_NAME" "$folder/run_examples.sh"; then
echo " OK OPIK_PROJECT_NAME in run_examples.sh"
elif grep -rq "OPIK_PROJECT_NAME" "$folder" --include="*.py" 2>/dev/null; then
echo " OK OPIK_PROJECT_NAME in .py config"
else
echo "::error::$folder must set OPIK_PROJECT_NAME — either export it in run_examples.sh or define it in a .py config file"
FAILED=1
fi
fi
# If litellm is declared as a dependency, OPIK_EXAMPLES_MODEL must be referenced.
if grep -q "litellm" "$folder/pyproject.toml" 2>/dev/null; then
if grep -rq "OPIK_EXAMPLES_MODEL" "$folder" --include="*.py" 2>/dev/null; then
echo " OK OPIK_EXAMPLES_MODEL referenced (litellm dep detected)"
else
echo "::error::$folder uses litellm but no .py file references OPIK_EXAMPLES_MODEL"
echo "::error::Add: os.environ.get(\"OPIK_EXAMPLES_MODEL\", \"<default-model>\") to your config"
FAILED=1
fi
fi
done
echo ""
if [[ "$FAILED" -ne 0 ]]; then
echo "Compliance check failed. Fix the errors above before merging."
exit 1
fi
echo "All compliance checks passed."
- name: Write step summary
if: always() && steps.changed-folders.outputs.folders != ''
run: |
echo "## Example Compliance Check" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "Folders checked: \`${{ steps.changed-folders.outputs.folders }}\`" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "**Rules:**" >> "$GITHUB_STEP_SUMMARY"
echo "- \`run_examples.sh\` must exist and contain \`set -e\`" >> "$GITHUB_STEP_SUMMARY"
echo "- \`OPIK_PROJECT_NAME\` must be set — via \`export\` in \`run_examples.sh\` (scripts) or as a constant in a \`.py\` config file (use-cases/guides)" >> "$GITHUB_STEP_SUMMARY"
echo "- \`README.md\` must exist" >> "$GITHUB_STEP_SUMMARY"
echo "- \`pyproject.toml\` must exist; \`requirements.txt\` is not allowed" >> "$GITHUB_STEP_SUMMARY"
echo "- If \`litellm\` is a dependency, a \`.py\` file must reference \`OPIK_EXAMPLES_MODEL\`" >> "$GITHUB_STEP_SUMMARY"