feat(nav): "Drucker" Link für Admin-Drucker-Verwaltung + getrennte Ac… #338
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| python: | |
| name: Python — lint, type, test | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Detect Python project | |
| id: pyproject | |
| run: | | |
| if [ -f backend/pyproject.toml ]; then | |
| echo "exists=true" >> "$GITHUB_OUTPUT" | |
| echo "path=backend" >> "$GITHUB_OUTPUT" | |
| elif [ -f pyproject.toml ]; then | |
| echo "exists=true" >> "$GITHUB_OUTPUT" | |
| echo "path=." >> "$GITHUB_OUTPUT" | |
| else | |
| echo "exists=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Set up Python (with cache) | |
| if: steps.pyproject.outputs.exists == 'true' | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| cache: pip | |
| cache-dependency-path: ${{ steps.pyproject.outputs.path }}/pyproject.toml | |
| - name: Set up Python (without cache, no pyproject.toml yet) | |
| if: steps.pyproject.outputs.exists != 'true' | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| - name: Install dependencies | |
| if: steps.pyproject.outputs.exists == 'true' | |
| working-directory: ${{ steps.pyproject.outputs.path }} | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -e ".[dev]" | |
| - name: Ruff lint | |
| if: steps.pyproject.outputs.exists == 'true' | |
| working-directory: ${{ steps.pyproject.outputs.path }} | |
| run: ruff check . | |
| - name: Ruff format check | |
| if: steps.pyproject.outputs.exists == 'true' | |
| working-directory: ${{ steps.pyproject.outputs.path }} | |
| run: ruff format --check . | |
| # Mypy is a HARD CI gate — strict mode is configured in pyproject.toml | |
| # ([tool.mypy] strict = true). We do not use `|| true` here; type-check | |
| # regressions must fail CI, not be merged silently. See CONTRIBUTING.md. | |
| - name: Mypy (strict, blocking) | |
| if: steps.pyproject.outputs.exists == 'true' | |
| working-directory: ${{ steps.pyproject.outputs.path }} | |
| run: mypy app/ | |
| # --cov-fail-under is a HARD CI gate. The 80% floor mirrors | |
| # [tool.coverage.report].fail_under in pyproject.toml — declaring it | |
| # again on the command line makes the failure surface in the test step | |
| # itself (with the exact `TOTAL` line) instead of in a separate | |
| # `coverage report` step that pytest-cov would silently swallow. | |
| # | |
| # -m "not slow" excludes wall-clock SSE timing tests (marked @pytest.mark.slow | |
| # in tests/integration/test_sse_flush.py). These tests use real asyncio | |
| # sleep timers and are too slow for every-commit CI. Run them separately | |
| # via: pytest -m slow tests/integration/test_sse_flush.py | |
| - name: Tests | |
| if: steps.pyproject.outputs.exists == 'true' | |
| working-directory: ${{ steps.pyproject.outputs.path }} | |
| run: pytest -m "not slow" --cov=app --cov-report=xml --cov-fail-under=80 --junitxml=junit.xml -o junit_family=legacy -v | |
| - name: Alembic — no pending migrations | |
| if: steps.pyproject.outputs.exists == 'true' | |
| working-directory: ${{ steps.pyproject.outputs.path }} | |
| run: | | |
| # alembic check compares SQLModel.metadata against the latest migration | |
| # head, but requires a DB at head first. Upgrade-then-check on a clean | |
| # data/ directory keeps CI deterministic. | |
| mkdir -p data | |
| alembic upgrade head | |
| alembic check | |
| - name: Upload coverage to Codecov | |
| if: always() && hashFiles('backend/coverage.xml', 'coverage.xml') != '' | |
| uses: codecov/codecov-action@v7 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| files: backend/coverage.xml,coverage.xml | |
| fail_ci_if_error: false | |
| flags: backend | |
| - name: Upload test results to Codecov | |
| if: ${{ !cancelled() && hashFiles('backend/junit.xml', 'junit.xml') != '' }} | |
| uses: codecov/test-results-action@v1 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| files: backend/junit.xml,junit.xml | |
| - name: No-op (no Python project yet — skipped) | |
| if: steps.pyproject.outputs.exists != 'true' | |
| run: echo "No pyproject.toml found — skipping Python checks for this branch" | |
| go: | |
| name: Go — vet, test | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Check for Go module | |
| id: go-check | |
| run: | | |
| if [ -f frontend/go.mod ]; then | |
| echo "exists=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "exists=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Set up Go | |
| if: steps.go-check.outputs.exists == 'true' | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version: stable | |
| cache-dependency-path: frontend/go.sum | |
| - name: go vet | |
| if: steps.go-check.outputs.exists == 'true' | |
| working-directory: frontend | |
| run: go vet ./... | |
| - name: Tests | |
| if: steps.go-check.outputs.exists == 'true' | |
| working-directory: frontend | |
| run: go test -race -coverprofile=coverage.out ./... | |
| oapi-codegen-drift: | |
| name: Check oapi-codegen output is up to date | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Check for Go module | |
| id: go-check | |
| run: | | |
| if [ -f frontend/go.mod ]; then | |
| echo "exists=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "exists=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Set up Go | |
| if: steps.go-check.outputs.exists == 'true' | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version: stable | |
| cache-dependency-path: frontend/go.sum | |
| - name: Regenerate client | |
| if: steps.go-check.outputs.exists == 'true' | |
| working-directory: frontend | |
| run: make gen-client | |
| - name: Fail if generated file differs | |
| if: steps.go-check.outputs.exists == 'true' | |
| run: git diff --exit-code frontend/internal/api/client.gen.go | |
| - name: No-op (no Go module yet) | |
| if: steps.go-check.outputs.exists != 'true' | |
| run: echo "No frontend/go.mod found — skipping oapi-codegen drift check" | |
| docker-build: | |
| name: Docker — build smoke test | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Check for Dockerfile | |
| id: docker-check | |
| run: | | |
| if [ -f Dockerfile ]; then | |
| echo "exists=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "exists=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Build (no push) | |
| if: steps.docker-check.outputs.exists == 'true' | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| push: false | |
| tags: label-printer-hub:ci | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| privacy-scan: | |
| name: Privacy / secret scan | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Scan for private network artefacts | |
| run: | | |
| set +e | |
| # IP-like patterns use PCRE negative lookbehind/lookahead so that | |
| # they only match real IPv4 addresses, not SNMP OIDs (1.3.6.1.…) | |
| # or version strings. | |
| ip_patterns=( | |
| '(?<![0-9.])172\.16\.[0-9]+\.[0-9]+(?![0-9.])' | |
| '(?<![0-9.])192\.168\.[0-9]+\.[0-9]+(?![0-9.])' | |
| '(?<![0-9.])10\.[0-9]+\.[0-9]+\.[0-9]+(?![0-9.])' | |
| '(?<![0-9.])100\.(?:6[4-9]|[7-9][0-9]|1[01][0-9]|12[0-7])\.[0-9]+\.[0-9]+(?![0-9.])' | |
| ) | |
| # String patterns use ERE (no boundary tricks needed) | |
| str_patterns=( | |
| 'hhdocker' | |
| 'hhplex' | |
| 'strausmann\.cloud' | |
| 'strausmann\.de' | |
| 'strausmann\.net' | |
| 'lager\.strausmann' | |
| ) | |
| # Files exempted because they DOCUMENT the policy (the patterns are | |
| # listed there as "don't do this" examples). All other markdown is | |
| # scanned — README, examples, wiki-mirrored content, ADRs, etc. | |
| exempt=( | |
| ':!docs/policies/privacy.md' | |
| ':!.gemini/styleguide.md' | |
| ':!.github/copilot-instructions.md' | |
| ':!.github/workflows/ci.yml' | |
| ) | |
| fail=0 | |
| for p in "${ip_patterns[@]}"; do | |
| hits=$(git grep -nIP "$p" -- "${exempt[@]}" || true) | |
| if [ -n "$hits" ]; then | |
| echo "::error title=Private IP detected::Pattern '$p' matched:" | |
| echo "$hits" | |
| fail=1 | |
| fi | |
| done | |
| for p in "${str_patterns[@]}"; do | |
| hits=$(git grep -nIE "$p" -- "${exempt[@]}" || true) | |
| if [ -n "$hits" ]; then | |
| echo "::error title=Private artefact detected::Pattern '$p' matched:" | |
| echo "$hits" | |
| fail=1 | |
| fi | |
| done | |
| exit $fail |