Skip to content

Commit d5b43c4

Browse files
committed
feat: add CLI arguments, modular refactor, test reorganization, and security scanning
- Add CLI support with short/long options (-s/--size, -p/--profile, etc.) - Split diskmark.sh into lib/ modules (args, validate, utils, profiles, detect, benchmark, output, update) - Reorganize tests into 6 concurrent jobs with optimized timing (100ms/500ms) - Integrate Trivy and Grype security scanning into docker-image.yml workflow
1 parent f8ed615 commit d5b43c4

14 files changed

Lines changed: 1851 additions & 1276 deletions

.github/copilot-instructions.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Copilot Instructions for docker-diskmark
2+
3+
## Project Overview
4+
5+
Docker DiskMark is a fio-based disk benchmarking tool packaged as a minimal Docker container. It provides CrystalDiskMark-like functionality for Linux systems.
6+
7+
**Container registries:**
8+
- Docker Hub: `e7db/diskmark` (tags only)
9+
- GHCR: `ghcr.io/e7db/diskmark` (all builds)
10+
11+
## Project Structure
12+
13+
```
14+
diskmark.sh # Main entry point (~70 lines)
15+
lib/
16+
├── args.sh # CLI argument parsing + help/version
17+
├── validate.sh # Input validation functions
18+
├── utils.sh # Utility functions (color, size conversion, cleanup)
19+
├── profiles.sh # Profile definitions (default, nvme, custom job)
20+
├── detect.sh # Drive/filesystem detection
21+
├── benchmark.sh # fio benchmark execution + warmup + result parsing
22+
├── output.sh # Output formatting (human/JSON/YAML/XML)
23+
└── update.sh # Update check functionality
24+
```
25+
26+
## Default Values
27+
28+
Key defaults (defined in Dockerfile ENV and scripts):
29+
- `TARGET=/disk` - Benchmark directory
30+
- `PROFILE=auto` - Auto-detect drive type
31+
- `IO=direct` - Direct I/O mode
32+
- `DATA=random` - Random data pattern
33+
- `SIZE=1G` - Test file size
34+
- `WARMUP=1` - Warmup enabled
35+
- `RUNTIME=5s` - Runtime per job
36+
- `UPDATE_CHECK=1` - Update check enabled
37+
38+
## Clean Code Principles
39+
40+
Follow these clean code principles when contributing:
41+
42+
### Single Responsibility
43+
- Each function should do one thing and do it well
44+
- Keep functions small and focused (ideally < 30 lines)
45+
- Separate concerns: parsing, validation, execution, output
46+
47+
### Meaningful Names
48+
- Use descriptive function names: `validate_size_string` not `check`
49+
- Use consistent naming conventions (snake_case for functions/variables)
50+
- Prefix validation functions with `validate_`
51+
- Prefix parsing functions with `parse_`
52+
53+
### DRY (Don't Repeat Yourself)
54+
- Extract common patterns into reusable functions
55+
- Use helper functions for repeated validation logic
56+
- Centralize error handling and output formatting
57+
58+
### Comments and Documentation
59+
- Functions should be self-documenting through clear names
60+
- Add comments only when explaining "why", not "what"
61+
- Keep help text and documentation in sync with code
62+
63+
### Error Handling
64+
- Fail fast with clear error messages
65+
- Validate inputs early before processing
66+
- Use consistent exit codes (0=success, 1=error)
67+
68+
### Code Organization
69+
- Group related functions together
70+
- Order: constants → helpers → validators → core logic → main
71+
- Keep configuration separate from logic
72+
73+
## Shell Script Best Practices
74+
75+
- Use `set -e` to exit on errors
76+
- Quote variables: `"$VAR"` not `$VAR`
77+
- Use `[[` for conditionals (bash)
78+
- Prefer `local` variables in functions
79+
- Use meaningful return codes
80+
- Avoid global state when possible
81+
82+
## Testing Guidelines
83+
84+
- All features should have corresponding tests in `.github/workflows/tests.yml`
85+
- Test both valid and invalid inputs
86+
- Test CLI arguments in all formats: `--key value`, `--key=value`, `-k value`
87+
- Use dry-run mode for input validation tests
88+
- Use minimal sizes/runtimes for actual benchmark tests
89+
90+
## Docker Best Practices
91+
92+
- Keep the container minimal (scratch-based)
93+
- Only include necessary binaries
94+
- Use multi-stage builds
95+
- Set appropriate defaults via ENV
96+
- Run as non-root user (65534:65534)
97+
98+
## CI/CD Workflows
99+
100+
- `tests.yml` - Input validation and benchmark tests
101+
- `docker-image.yml` - Build and push to GHCR (always) and Docker Hub (tags only)
102+
- `codeql.yml` - Security scanning
103+
104+
## Output Formats
105+
106+
The tool supports multiple output formats:
107+
- Human-readable (default): colored, with emojis
108+
- JSON: structured, machine-readable
109+
- YAML: structured, human-friendly
110+
- XML: structured, enterprise-compatible
111+
112+
When modifying output, ensure all formats are updated consistently.

.github/workflows/docker-image.yml

Lines changed: 80 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,88 +17,141 @@ permissions:
1717
contents: read
1818
packages: write
1919
pull-requests: write
20+
security-events: write
2021

2122
env:
2223
PLATFORMS: linux/amd64,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x
2324

2425
jobs:
2526
build:
27+
name: Build and Push Docker Image
2628
runs-on: ubuntu-latest
2729
env:
28-
HAS_DOCKERHUB_SECRETS: ${{ github.event_name != 'pull_request' || github.repository == github.event.pull_request.head.repo.full_name }}
30+
IS_TAG: ${{ startsWith(github.ref, 'refs/tags/') }}
31+
SHOULD_PUSH: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
2932
steps:
3033
- name: Checkout
31-
uses: actions/checkout@v4
34+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
3235
- name: Set up QEMU
33-
uses: docker/setup-qemu-action@v3
36+
uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3.4.0
3437
- name: Set up Docker Buildx
35-
uses: docker/setup-buildx-action@v3
38+
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.9.0
3639
- name: Login to Docker Hub
37-
if: ${{ env.HAS_DOCKERHUB_SECRETS }}
38-
uses: docker/login-action@v3
40+
if: ${{ env.IS_TAG }}
41+
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
3942
with:
4043
username: ${{ secrets.DOCKERHUB_USERNAME }}
4144
password: ${{ secrets.DOCKERHUB_TOKEN }}
4245
- name: Login to GitHub Container Registry
43-
uses: docker/login-action@v3
46+
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
4447
with:
4548
registry: ghcr.io
4649
username: ${{ github.repository_owner }}
4750
password: ${{ secrets.GITHUB_TOKEN }}
4851
- name: Extract Docker metadata
4952
id: meta
50-
uses: docker/metadata-action@v5
53+
uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5.6.1
5154
with:
5255
images: |
5356
name=${{ vars.GHCR_IMAGE }}
5457
name=${{ vars.DOCKERHUB_IMAGE }},enable=${{ startsWith(github.ref, 'refs/tags/') }}
5558
labels: |
5659
org.opencontainers.image.title=docker-diskmark
5760
org.opencontainers.image.description=A disk benchmarking tool for Docker
58-
org.opencontainers.image.revision=${{ env.SHA }}
61+
org.opencontainers.image.revision=${{ github.sha }}
5962
tags: |
6063
type=semver,pattern={{version}}
6164
type=semver,pattern={{major}}.{{minor}}
6265
type=semver,pattern={{major}}
6366
type=ref,event=pr
6467
type=raw,value=latest,enable={{is_default_branch}}
68+
type=sha,format=long,prefix=sha-
6569
- name: Determine version
6670
id: version
6771
run: |
6872
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
69-
echo "version=${{ github.ref_name }}" >> $GITHUB_OUTPUT
73+
VERSION="${{ github.ref_name }}"
74+
VERSION="${VERSION#v}"
7075
else
71-
echo "version=${{ github.sha }}" >> $GITHUB_OUTPUT
76+
GIT_DESC=$(git describe --tags --always 2>/dev/null)
77+
if [[ "$GIT_DESC" =~ ^v?([0-9]+\.[0-9]+\.[0-9]+)-([0-9]+)-g([a-f0-9]+)$ ]]; then
78+
VERSION="${BASH_REMATCH[1]}-dev.${BASH_REMATCH[2]}+${BASH_REMATCH[3]}"
79+
else
80+
VERSION="0.0.0-dev+${GITHUB_SHA}"
81+
fi
7282
fi
83+
echo "version=$VERSION" >> $GITHUB_OUTPUT
7384
- name: Build and push Docker image
74-
uses: docker/build-push-action@v5
85+
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v5.4.0
7586
with:
7687
context: .
7788
platforms: ${{ env.PLATFORMS }}
7889
pull: true
7990
cache-from: type=gha
8091
cache-to: type=gha
81-
push: true
92+
push: ${{ env.SHOULD_PUSH }}
8293
tags: ${{ steps.meta.outputs.tags }}
8394
labels: ${{ steps.meta.outputs.labels }}
8495
build-args: |
8596
VERSION=${{ steps.version.outputs.version }}
86-
- name: Docker Scout
87-
id: docker-scout
88-
if: ${{ github.event_name == 'pull_request' }}
89-
uses: docker/scout-action@v1
90-
with:
91-
command: cves,compare
92-
image: ${{ steps.meta.outputs.tags }}
93-
to: ${{ vars.GHCR_IMAGE }}:latest
94-
ignore-unchanged: true
95-
only-fixed: true
96-
write-comment: true
97-
github-token: ${{ secrets.GITHUB_TOKEN }}
97+
98+
update-description:
99+
name: Update DockerHub Description
100+
runs-on: ubuntu-latest
101+
needs: build
102+
if: ${{ github.ref == 'refs/heads/main' }}
103+
steps:
98104
- name: Update repo description
99-
if: ${{ github.ref == 'refs/heads/main' && env.HAS_DOCKERHUB_SECRETS }}
100-
uses: peter-evans/dockerhub-description@v4
105+
uses: peter-evans/dockerhub-description@e98e4d1628a5f3be2be7c231e50981aee98723ae # v4.0.2
101106
with:
102107
username: ${{ secrets.DOCKERHUB_USERNAME }}
103108
password: ${{ secrets.DOCKERHUB_TOKEN }}
104109
repository: ${{ vars.DOCKERHUB_IMAGE }}
110+
111+
scan:
112+
name: Security Scan (${{ matrix.scanner }})
113+
runs-on: ubuntu-latest
114+
needs: build
115+
strategy:
116+
fail-fast: false
117+
matrix:
118+
scanner: [trivy, grype]
119+
steps:
120+
- name: Checkout
121+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
122+
- name: Login to GitHub Container Registry
123+
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
124+
with:
125+
registry: ghcr.io
126+
username: ${{ github.repository_owner }}
127+
password: ${{ secrets.GITHUB_TOKEN }}
128+
129+
# Trivy
130+
- name: Run Trivy vulnerability scanner
131+
if: ${{ matrix.scanner == 'trivy' }}
132+
uses: aquasecurity/trivy-action@18f2510ee396bbf400402947b394f2dd8c87dbb0 # 0.30.0
133+
with:
134+
image-ref: ${{ vars.GHCR_IMAGE }}:sha-${{ github.sha }}
135+
format: sarif
136+
output: trivy-results.sarif
137+
severity: CRITICAL,HIGH,MEDIUM
138+
- name: Upload Trivy scan results
139+
if: ${{ matrix.scanner == 'trivy' && always() }}
140+
uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.1
141+
with:
142+
sarif_file: trivy-results.sarif
143+
144+
# Grype
145+
- name: Run Grype vulnerability scanner
146+
if: ${{ matrix.scanner == 'grype' }}
147+
id: grype
148+
uses: anchore/scan-action@abae793926ec39a78ab18002bc7fc45bbbd94342 # v6.0.0
149+
with:
150+
image: ${{ vars.GHCR_IMAGE }}:sha-${{ github.sha }}
151+
fail-build: false
152+
severity-cutoff: medium
153+
- name: Upload Grype scan results
154+
if: ${{ matrix.scanner == 'grype' && always() }}
155+
uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.1
156+
with:
157+
sarif_file: ${{ steps.grype.outputs.sarif }}

0 commit comments

Comments
 (0)