Skip to content

Commit a6feb5f

Browse files
committed
Improve GitHub Actions workflows
- Split monolithic packages.yml into separate workflows (test, build, security, release) - Add multi-OS matrix strategy (Ubuntu, macOS, Windows) - Add comprehensive pip caching across all platforms - Add network-aware protocol tests with proper error handling - Add security scanning (Bandit, Safety, CodeQL) - Add automated PyPI and Docker publishing on releases - Add smoke tests for built binaries - Keep old packages.yml as backup
1 parent 12f97d3 commit a6feb5f

File tree

5 files changed

+374
-0
lines changed

5 files changed

+374
-0
lines changed

.github/workflows/build.yml

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
name: Build Packages
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
tags: [ 'v*' ]
7+
pull_request:
8+
branches: [ master ]
9+
10+
jobs:
11+
build:
12+
runs-on: ${{ matrix.os }}
13+
strategy:
14+
fail-fast: false
15+
matrix:
16+
os: [ubuntu-latest, macos-latest, windows-latest]
17+
python-version: ["3.11"] # Use single Python version for builds
18+
19+
steps:
20+
- uses: actions/checkout@v5
21+
22+
- name: Set up Python ${{ matrix.python-version }}
23+
uses: actions/setup-python@v6
24+
with:
25+
python-version: ${{ matrix.python-version }}
26+
27+
- name: Cache pip packages
28+
uses: actions/cache@v4
29+
with:
30+
path: |
31+
~/.cache/pip
32+
~/AppData/Local/pip/Cache
33+
~/Library/Caches/pip
34+
key: ${{ runner.os }}-pip-build-${{ hashFiles('requirements.txt', 'pyproject.toml') }}
35+
restore-keys: |
36+
${{ runner.os }}-pip-build-
37+
${{ runner.os }}-pip-
38+
39+
- name: Install build dependencies
40+
run: |
41+
python -m pip install --upgrade pip
42+
pip install pyinstaller
43+
pip install -r requirements.txt
44+
45+
- name: Build packages (Unix)
46+
if: runner.os != 'Windows'
47+
run: |
48+
chmod +x build-pkgs.sh
49+
./build-pkgs.sh
50+
shell: bash
51+
52+
- name: Build packages (Windows)
53+
if: runner.os == 'Windows'
54+
run: |
55+
./build-pkgs.sh
56+
shell: bash
57+
58+
- name: Test built binaries (Unix)
59+
if: runner.os != 'Windows'
60+
run: |
61+
# Find the built package directory
62+
PKG_DIR=$(find pkg -name "dnsdiag-*" -type d | head -1)
63+
if [ -d "$PKG_DIR" ]; then
64+
echo "Testing built binaries in $PKG_DIR"
65+
$PKG_DIR/dnsping --help
66+
$PKG_DIR/dnstraceroute --help
67+
$PKG_DIR/dnseval --help
68+
else
69+
echo "No package directory found"
70+
exit 1
71+
fi
72+
shell: bash
73+
74+
- name: Test built binaries (Windows)
75+
if: runner.os == 'Windows'
76+
run: |
77+
# Find the built package directory
78+
$PKG_DIR = Get-ChildItem -Path pkg -Directory -Name "dnsdiag-*" | Select-Object -First 1
79+
if ($PKG_DIR) {
80+
Write-Host "Testing built binaries in pkg/$PKG_DIR"
81+
& "pkg/$PKG_DIR/dnsping.exe" --help
82+
& "pkg/$PKG_DIR/dnstraceroute.exe" --help
83+
& "pkg/$PKG_DIR/dnseval.exe" --help
84+
} else {
85+
Write-Host "No package directory found"
86+
exit 1
87+
}
88+
shell: pwsh
89+
90+
- name: Upload build artifacts
91+
uses: actions/upload-artifact@v4
92+
with:
93+
name: dnsdiag-${{ runner.os }}-${{ runner.arch }}
94+
path: pkg/dnsdiag-*.*
95+
retention-days: 7
96+
97+
- name: Upload to release (on tag)
98+
if: startsWith(github.ref, 'refs/tags/')
99+
uses: softprops/action-gh-release@v2
100+
with:
101+
files: pkg/dnsdiag-*.*
102+
env:
103+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/release.yml

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
name: Release
2+
3+
on:
4+
release:
5+
types: [published]
6+
workflow_dispatch: # Allow manual triggering
7+
8+
jobs:
9+
pypi-publish:
10+
name: Publish to PyPI
11+
runs-on: ubuntu-latest
12+
environment: release
13+
permissions:
14+
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
15+
16+
steps:
17+
- uses: actions/checkout@v5
18+
19+
- name: Set up Python
20+
uses: actions/setup-python@v6
21+
with:
22+
python-version: "3.11"
23+
24+
- name: Cache pip packages
25+
uses: actions/cache@v4
26+
with:
27+
path: ~/.cache/pip
28+
key: ubuntu-latest-pip-release-${{ hashFiles('requirements.txt', 'pyproject.toml') }}
29+
restore-keys: |
30+
ubuntu-latest-pip-release-
31+
ubuntu-latest-pip-
32+
33+
- name: Install build dependencies
34+
run: |
35+
python -m pip install --upgrade pip
36+
pip install build twine
37+
38+
- name: Build source and wheel distributions
39+
run: |
40+
python -m build
41+
42+
- name: Check distributions
43+
run: |
44+
python -m twine check dist/*
45+
46+
- name: Publish to PyPI
47+
uses: pypa/gh-action-pypi-publish@release/v1
48+
# Only publish on actual releases, not manual workflow dispatch
49+
if: github.event_name == 'release'
50+
51+
docker-publish:
52+
name: Publish Docker image
53+
runs-on: ubuntu-latest
54+
55+
steps:
56+
- uses: actions/checkout@v5
57+
58+
- name: Set up Docker Buildx
59+
uses: docker/setup-buildx-action@v3
60+
61+
- name: Log in to Docker Hub
62+
if: github.event_name == 'release'
63+
uses: docker/login-action@v3
64+
with:
65+
username: ${{ secrets.DOCKERHUB_USERNAME }}
66+
password: ${{ secrets.DOCKERHUB_TOKEN }}
67+
68+
- name: Extract metadata
69+
id: meta
70+
uses: docker/metadata-action@v5
71+
with:
72+
images: farrokhi/dnsdiag
73+
tags: |
74+
type=ref,event=branch
75+
type=ref,event=pr
76+
type=semver,pattern={{version}}
77+
type=semver,pattern={{major}}.{{minor}}
78+
type=semver,pattern={{major}}
79+
80+
- name: Build and push Docker image
81+
uses: docker/build-push-action@v5
82+
with:
83+
context: .
84+
platforms: linux/amd64,linux/arm64
85+
push: ${{ github.event_name == 'release' }}
86+
tags: ${{ steps.meta.outputs.tags }}
87+
labels: ${{ steps.meta.outputs.labels }}
88+
cache-from: type=gha
89+
cache-to: type=gha,mode=max

.github/workflows/security.yml

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
name: Security Scan
2+
3+
on:
4+
push:
5+
branches: [ master, develop ]
6+
pull_request:
7+
branches: [ master ]
8+
schedule:
9+
- cron: '0 2 * * 1' # Weekly security scan on Mondays
10+
11+
jobs:
12+
security:
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- uses: actions/checkout@v5
17+
18+
- name: Set up Python
19+
uses: actions/setup-python@v6
20+
with:
21+
python-version: "3.11"
22+
23+
- name: Cache pip packages
24+
uses: actions/cache@v4
25+
with:
26+
path: ~/.cache/pip
27+
key: ubuntu-latest-pip-security-${{ hashFiles('requirements.txt', 'pyproject.toml') }}
28+
restore-keys: |
29+
ubuntu-latest-pip-security-
30+
ubuntu-latest-pip-
31+
32+
- name: Install dependencies
33+
run: |
34+
python -m pip install --upgrade pip
35+
pip install bandit safety
36+
pip install -r requirements.txt
37+
38+
- name: Run Bandit security scan
39+
run: |
40+
echo "Running Bandit security scan..."
41+
bandit -r . -f json -o bandit-report.json -ll || true
42+
bandit -r . -f txt -ll || echo "Bandit found some issues, check the detailed report"
43+
44+
- name: Run Safety dependency check
45+
run: |
46+
echo "Running Safety dependency vulnerability check..."
47+
safety check --json --output safety-report.json || true
48+
safety check || echo "Safety found some vulnerabilities, check the detailed report"
49+
50+
- name: Upload security reports
51+
if: always()
52+
uses: actions/upload-artifact@v4
53+
with:
54+
name: security-reports
55+
path: |
56+
bandit-report.json
57+
safety-report.json
58+
retention-days: 30
59+
60+
codeql:
61+
name: CodeQL Analysis
62+
runs-on: ubuntu-latest
63+
permissions:
64+
actions: read
65+
contents: read
66+
security-events: write
67+
68+
steps:
69+
- name: Checkout repository
70+
uses: actions/checkout@v5
71+
72+
- name: Initialize CodeQL
73+
uses: github/codeql-action/init@v3
74+
with:
75+
languages: python
76+
77+
- name: Autobuild
78+
uses: github/codeql-action/autobuild@v3
79+
80+
- name: Perform CodeQL Analysis
81+
uses: github/codeql-action/analyze@v3

.github/workflows/test.yml

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
branches: [ master, develop ]
6+
pull_request:
7+
branches: [ master ]
8+
9+
jobs:
10+
test:
11+
runs-on: ${{ matrix.os }}
12+
strategy:
13+
fail-fast: false
14+
matrix:
15+
os: [ubuntu-latest, macos-latest, windows-latest]
16+
python-version: ["3.10", "3.11", "3.12", "3.13"]
17+
18+
steps:
19+
- uses: actions/checkout@v5
20+
21+
- name: Set up Python ${{ matrix.python-version }}
22+
uses: actions/setup-python@v6
23+
with:
24+
python-version: ${{ matrix.python-version }}
25+
26+
- name: Cache pip packages
27+
uses: actions/cache@v4
28+
with:
29+
path: |
30+
~/.cache/pip
31+
~/AppData/Local/pip/Cache
32+
~/Library/Caches/pip
33+
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt', 'pyproject.toml') }}
34+
restore-keys: |
35+
${{ runner.os }}-pip-
36+
37+
- name: Install dependencies
38+
run: |
39+
python -m pip install --upgrade pip
40+
pip install flake8 pytest pytest-cov
41+
pip install -r requirements.txt
42+
43+
- name: Lint with flake8
44+
run: |
45+
# stop the build if there are Python syntax errors or undefined names
46+
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
47+
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
48+
flake8 . --count --exit-zero --max-complexity=60 --max-line-length=127 --statistics
49+
50+
- name: Run unit tests (if they exist)
51+
run: |
52+
if [ -d "tests" ]; then
53+
pytest tests/ --cov=dnsdiag --cov-report=xml --cov-report=term
54+
else
55+
echo "No tests directory found - skipping unit tests"
56+
fi
57+
shell: bash
58+
59+
- name: Test imports and basic functionality
60+
run: |
61+
python -c "import dnsdiag.dns; print('DNS module imports successfully')"
62+
python -c "import dnsping; print('dnsping imports successfully')"
63+
python -c "import dnstraceroute; print('dnstraceroute imports successfully')"
64+
python -c "import dnseval; print('dnseval imports successfully')"
65+
shell: bash
66+
67+
- name: Run help commands
68+
run: |
69+
python dnsping.py --help
70+
python dnstraceroute.py --help
71+
python dnseval.py --help
72+
shell: bash
73+
74+
- name: Run basic protocol tests (Linux/Mac only)
75+
if: runner.os != 'Windows'
76+
run: |
77+
echo "Testing basic UDP DNS functionality..."
78+
timeout 15s python dnsping.py -s 8.8.8.8 -c 1 -w 3 google.com || echo "UDP test failed/timed out - may be network issue"
79+
80+
echo "Testing TCP DNS functionality..."
81+
timeout 15s python dnsping.py -T -s 8.8.8.8 -c 1 -w 3 google.com || echo "TCP test failed/timed out - may be network issue"
82+
83+
echo "Testing DoT DNS functionality..."
84+
timeout 15s python dnsping.py -X -s 8.8.8.8 -c 1 -w 3 google.com || echo "DoT test failed/timed out - may be network issue"
85+
86+
echo "Testing DoH DNS functionality..."
87+
timeout 15s python dnsping.py -H -s 8.8.8.8 -c 1 -w 3 google.com || echo "DoH test failed/timed out - may be network issue"
88+
89+
echo "Testing DoQ DNS functionality (AdGuard)..."
90+
timeout 15s python dnsping.py -Q -s 94.140.14.14 -c 1 -w 3 google.com || echo "DoQ test failed/timed out - may be network issue"
91+
92+
echo "Testing DoH3 DNS functionality..."
93+
timeout 15s python dnsping.py -3 -s 8.8.8.8 -c 1 -w 3 google.com || echo "DoH3 test failed/timed out - may be network issue"
94+
shell: bash
95+
96+
- name: Run dnstraceroute basic test (Linux only)
97+
if: runner.os == 'Linux'
98+
run: |
99+
echo "Testing dnstraceroute UDP functionality..."
100+
timeout 20s python dnstraceroute.py -s 8.8.8.8 -c 3 -w 2 google.com || echo "dnstraceroute test failed/timed out - may be network or permission issue"
101+
shell: bash

0 commit comments

Comments
 (0)