Skip to content

Commit b239275

Browse files
committed
feat: add composite actions, enhanced governance, and GitHub Issue reports
- Add composite actions: security-scan, sync-settings, update-pre-commit - Replace SMTP email with GitHub Issues for drift reporting - Add label sync, default branch check, vulnerability alerts, metadata checks - Enhance README with detailed purpose for every enforced setting - Add baseline schema validation to quality checks - Refactor workflows to use composite actions
1 parent c464a06 commit b239275

12 files changed

Lines changed: 657 additions & 165 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Security Scan
2+
description: Run SAST and SCA security scans
3+
4+
inputs:
5+
scan-path:
6+
description: Path to scan
7+
required: false
8+
default: "."
9+
10+
runs:
11+
using: composite
12+
steps:
13+
- name: Run Semgrep SAST
14+
uses: semgrep/semgrep-action@713efdd345f3035192eaa63f56867b88e63e4e5d # v1.0.0
15+
with:
16+
config: auto
17+
18+
- name: Run Trivy vulnerability scanner
19+
uses: aquasecurity/trivy-action@18f2510ee396bbf400402947e7f3b01483832965 # v0.31.0
20+
with:
21+
scan-type: fs
22+
scan-ref: ${{ inputs.scan-path }}
23+
format: sarif
24+
output: trivy-results.sarif
25+
26+
- name: Upload Trivy results to GitHub Security
27+
uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
28+
if: always()
29+
with:
30+
sarif_file: trivy-results.sarif
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: Sync Repository Settings
2+
description: Compare and apply GitHub settings across all repos against a baseline
3+
4+
inputs:
5+
mode:
6+
description: "Run mode: --dry-run or --apply"
7+
required: true
8+
default: "--dry-run"
9+
github_token:
10+
description: PAT with repo and admin scopes
11+
required: true
12+
13+
outputs:
14+
report_file:
15+
description: Path to the generated report
16+
value: ${{ steps.sync.outputs.report_file }}
17+
total_repos:
18+
description: Number of repos scanned
19+
value: ${{ steps.parse.outputs.total_repos }}
20+
compliant:
21+
description: Number of compliant repos
22+
value: ${{ steps.parse.outputs.compliant }}
23+
drift:
24+
description: Number of repos with drift
25+
value: ${{ steps.parse.outputs.drift }}
26+
has_drift:
27+
description: Whether any drift was detected
28+
value: ${{ steps.parse.outputs.has_drift }}
29+
30+
runs:
31+
using: composite
32+
steps:
33+
- name: Run settings sync
34+
id: sync
35+
shell: bash
36+
env:
37+
GH_TOKEN: ${{ inputs.github_token }}
38+
REPORT_FILE: reports/sync-report.md
39+
run: |
40+
./scripts/sync-repo-settings.sh "${{ inputs.mode }}"
41+
echo "report_file=reports/sync-report.md" >> "$GITHUB_OUTPUT"
42+
43+
- name: Parse report
44+
id: parse
45+
shell: bash
46+
run: ./scripts/generate-report.sh reports/sync-report.md
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Update Pre-commit Hooks Composite Action
2+
description: Updates pre-commit hook versions and creates a PR
3+
4+
inputs:
5+
github_token:
6+
description: GitHub token for creating PRs
7+
required: true
8+
9+
runs:
10+
using: composite
11+
steps:
12+
- name: Set up Python
13+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
14+
with:
15+
python-version: "3.x"
16+
17+
- name: Install pre-commit
18+
shell: bash
19+
run: pip install pre-commit
20+
21+
- name: Update hooks
22+
shell: bash
23+
run: pre-commit autoupdate
24+
25+
- name: Create Pull Request
26+
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
27+
with:
28+
token: ${{ inputs.github_token }}
29+
commit-message: "chore: update pre-commit hook versions"
30+
title: "chore: update pre-commit hook versions"
31+
body: |
32+
Automated update of pre-commit hook versions.
33+
34+
Review the changes to `.pre-commit-config.yaml` and merge if CI passes.
35+
branch: chore/update-pre-commit-hooks
36+
delete-branch: true

.github/workflows/quality-checks.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,22 @@ jobs:
7979
fi
8080
done
8181
82+
- name: Validate baseline schema
83+
run: |
84+
ERRORS=0
85+
for section in repo_settings security branch_protection labels required_files; do
86+
if ! jq -e ".$section" config/baseline.json > /dev/null 2>&1; then
87+
echo "ERROR: Missing section '$section' in baseline.json"
88+
ERRORS=$((ERRORS + 1))
89+
else
90+
echo "OK: section '$section' present"
91+
fi
92+
done
93+
if [ "$ERRORS" -gt 0 ]; then
94+
echo "ERROR: baseline.json schema validation failed"
95+
exit 1
96+
fi
97+
8298
actions-security:
8399
name: Actions Security
84100
runs-on: ubuntu-latest

.github/workflows/security.yml

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ on:
99
permissions:
1010
contents: read
1111
security-events: write
12+
actions: read
1213

1314
jobs:
1415
security-scan:
@@ -17,21 +18,10 @@ jobs:
1718
steps:
1819
- name: Checkout
1920
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
20-
21-
- name: Run Semgrep
22-
uses: returntocorp/semgrep-action@713efdd345f3035192eaa63f56867b88e63e4e5d # v1.0.0
23-
with:
24-
config: auto
25-
26-
- name: Run Trivy
27-
uses: aquasecurity/trivy-action@18f2510ee396bbf400402947e7f3b01483832965 # v0.31.0
2821
with:
29-
scan-type: fs
30-
format: sarif
31-
output: trivy-results.sarif
22+
persist-credentials: false
3223

33-
- name: Upload Trivy SARIF
34-
uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
35-
if: always()
24+
- name: Run Security Scan
25+
uses: ./.github/actions/security-scan
3626
with:
37-
sarif_file: trivy-results.sarif
27+
scan-path: "."

.github/workflows/sync-settings.yml

Lines changed: 94 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -17,104 +17,139 @@ on:
1717

1818
permissions:
1919
contents: read
20+
issues: write
2021

2122
jobs:
2223
sync:
2324
name: Sync Settings
2425
runs-on: ubuntu-latest
25-
permissions:
26-
contents: read
2726
steps:
2827
- name: Checkout
2928
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
3029

31-
- name: Sync repository settings
32-
env:
33-
GH_TOKEN: ${{ secrets.ORG_SETTINGS_PAT }}
34-
REPORT_FILE: reports/sync-report.md
35-
run: |
36-
MODE="${{ github.event.inputs.mode || '--apply' }}"
37-
./scripts/sync-repo-settings.sh "$MODE"
38-
39-
- name: Parse report
40-
id: report
41-
run: ./scripts/generate-report.sh reports/sync-report.md
30+
- name: Run settings sync
31+
id: sync
32+
uses: ./.github/actions/sync-settings
33+
with:
34+
mode: ${{ github.event.inputs.mode || '--apply' }}
35+
github_token: ${{ secrets.ORG_SETTINGS_PAT }}
4236

4337
- name: Upload report artifact
4438
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
4539
with:
46-
name: sync-report
40+
name: sync-report-${{ github.run_number }}
4741
path: reports/sync-report.md
4842
retention-days: 90
4943

50-
- name: Send email report
44+
- name: Post job summary
5145
if: always()
52-
uses: dawidd6/action-send-mail@v3.12.0
53-
with:
54-
server_address: smtp.gmail.com
55-
server_port: 587
56-
username: ${{ secrets.EMAIL_USERNAME }}
57-
password: ${{ secrets.EMAIL_PASSWORD }}
58-
subject: |
59-
GitHub Settings Sync Report — ${{ steps.report.outputs.drift > 0 && 'Drift Detected' || 'All Compliant' }}
60-
to: gamaware@gmail.com
61-
from: GitHub Org Settings <${{ secrets.EMAIL_USERNAME }}>
62-
body: |
63-
GitHub Organization Settings Sync Report
64-
==========================================
65-
66-
Date: ${{ github.event.head_commit.timestamp || github.event.repository.updated_at }}
67-
Mode: ${{ github.event.inputs.mode || '--apply' }}
68-
Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
69-
70-
Repositories scanned: ${{ steps.report.outputs.total_repos }}
71-
Compliant: ${{ steps.report.outputs.compliant }}
72-
Drift detected: ${{ steps.report.outputs.drift }}
73-
74-
Full report is attached and available as a workflow artifact.
75-
attachments: reports/sync-report.md
46+
run: |
47+
{
48+
echo "## Settings Sync Results"
49+
echo ""
50+
echo "| Metric | Value |"
51+
echo "| --- | --- |"
52+
echo "| Repositories scanned | ${{ steps.sync.outputs.total_repos }} |"
53+
echo "| Compliant | ${{ steps.sync.outputs.compliant }} |"
54+
echo "| Drift detected | ${{ steps.sync.outputs.drift }} |"
55+
echo "| Mode | ${{ github.event.inputs.mode || '--apply' }} |"
56+
echo ""
57+
echo "### Full Report"
58+
echo ""
59+
cat reports/sync-report.md
60+
} >> "$GITHUB_STEP_SUMMARY"
61+
62+
- name: Create or update drift issue
63+
if: steps.sync.outputs.has_drift == 'true'
64+
env:
65+
GH_TOKEN: ${{ secrets.ORG_SETTINGS_PAT }}
66+
run: |
67+
TITLE="chore: settings drift detected — $(date '+%Y-%m-%d')"
68+
BODY=$(cat <<'ISSUE_EOF'
69+
## Settings Drift Report
70+
71+
**Run**: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
72+
**Mode**: ${{ github.event.inputs.mode || '--apply' }}
73+
**Repos with drift**: ${{ steps.sync.outputs.drift }} / ${{ steps.sync.outputs.total_repos }}
74+
75+
See the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for the full report.
76+
77+
ISSUE_EOF
78+
)
79+
80+
# Close previous drift issues
81+
gh issue list --label "settings-drift" --state open --json number --jq '.[].number' | while read -r num; do
82+
gh issue close "$num" --comment "Superseded by new sync run."
83+
done
84+
85+
# Create new issue
86+
gh issue create --title "$TITLE" --body "$BODY" --label "settings-drift"
87+
88+
- name: Close drift issue if compliant
89+
if: steps.sync.outputs.has_drift == 'false'
90+
env:
91+
GH_TOKEN: ${{ secrets.ORG_SETTINGS_PAT }}
92+
run: |
93+
gh issue list --label "settings-drift" --state open --json number --jq '.[].number' | while read -r num; do
94+
gh issue close "$num" --comment "All repositories are now compliant."
95+
done
7696
7797
new-repo-check:
7898
name: Discover New Repositories
7999
runs-on: ubuntu-latest
80-
permissions:
81-
contents: read
82100
steps:
83101
- name: Checkout
84102
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
85103

86104
- name: Check for new repos
105+
id: newrepos
87106
env:
88107
GH_TOKEN: ${{ secrets.ORG_SETTINGS_PAT }}
89108
run: |
90-
echo "## New Repository Discovery" > reports/new-repos.md
91-
echo "" >> reports/new-repos.md
92-
93-
# Get all current repos
94-
gh repo list gamaware --no-archived --json name,createdAt --jq '.[] | "\(.name) (created: \(.createdAt))"' --limit 200 > /tmp/all-repos.txt
95-
96-
# Check repos created in the last 7 days
97-
WEEK_AGO=$(date -u -d '7 days ago' '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || date -u -v-7d '+%Y-%m-%dT%H:%M:%SZ')
109+
mkdir -p reports
110+
{
111+
echo "## New Repository Discovery"
112+
echo ""
113+
} > reports/new-repos.md
114+
115+
WEEK_AGO=$(date -u -d '7 days ago' '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null \
116+
|| date -u -v-7d '+%Y-%m-%dT%H:%M:%SZ')
98117
NEW_REPOS=$(gh repo list gamaware --no-archived --json name,createdAt \
99-
--jq "[.[] | select(.createdAt > \"$WEEK_AGO\")] | .[].name" --limit 200 || echo "")
118+
--jq "[.[] | select(.createdAt > \"$WEEK_AGO\")] | .[].name" \
119+
--limit 200 || echo "")
100120
101121
if [ -n "$NEW_REPOS" ]; then
102-
echo "New repositories found in the last 7 days:" >> reports/new-repos.md
103-
echo "" >> reports/new-repos.md
104-
echo "$NEW_REPOS" | while read -r repo; do
105-
echo "- **$repo**" >> reports/new-repos.md
106-
done
107-
echo "" >> reports/new-repos.md
108-
echo "These repositories will be included in the next settings sync." >> reports/new-repos.md
122+
echo "has_new=true" >> "$GITHUB_OUTPUT"
123+
{
124+
echo "New repositories found in the last 7 days:"
125+
echo ""
126+
echo "$NEW_REPOS" | while read -r repo; do
127+
echo "- **$repo**"
128+
done
129+
echo ""
130+
echo "These repositories will be included in the next settings sync."
131+
} >> reports/new-repos.md
109132
else
133+
echo "has_new=false" >> "$GITHUB_OUTPUT"
110134
echo "No new repositories found in the last 7 days." >> reports/new-repos.md
111135
fi
112136
113-
cat reports/new-repos.md
137+
cat reports/new-repos.md >> "$GITHUB_STEP_SUMMARY"
138+
139+
- name: Create issue for new repos
140+
if: steps.newrepos.outputs.has_new == 'true'
141+
env:
142+
GH_TOKEN: ${{ secrets.ORG_SETTINGS_PAT }}
143+
run: |
144+
BODY=$(cat reports/new-repos.md)
145+
gh issue create \
146+
--title "chore: new repositories discovered — $(date '+%Y-%m-%d')" \
147+
--body "$BODY" \
148+
--label "new-repo"
114149
115150
- name: Upload new repos report
116151
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
117152
with:
118-
name: new-repos-report
153+
name: new-repos-report-${{ github.run_number }}
119154
path: reports/new-repos.md
120155
retention-days: 30

0 commit comments

Comments
 (0)