Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Copilot Instructions for docker-diskmark

## Project Overview

Docker DiskMark is a fio-based disk benchmarking tool packaged as a minimal Docker container. It provides CrystalDiskMark-like functionality for Linux systems.

**Container registries:**
- Docker Hub: `e7db/diskmark` (tags only)
- GHCR: `ghcr.io/e7db/diskmark` (all builds)

## Project Structure

```
diskmark.sh # Main entry point (~70 lines)
lib/
├── args.sh # CLI argument parsing + help/version
├── validate.sh # Input validation functions
├── utils.sh # Utility functions (color, size conversion, cleanup)
├── profiles.sh # Profile definitions (default, nvme, custom job)
├── detect.sh # Drive/filesystem detection
├── benchmark.sh # fio benchmark execution + warmup + result parsing
├── output.sh # Output formatting (human/JSON/YAML/XML)
└── update.sh # Update check functionality
```

## Default Values

Key defaults (defined in Dockerfile ENV and scripts):
- `TARGET=/disk` - Benchmark directory
- `PROFILE=auto` - Auto-detect drive type
- `IO=direct` - Direct I/O mode
- `DATA=random` - Random data pattern
- `SIZE=1G` - Test file size
- `WARMUP=1` - Warmup enabled
- `RUNTIME=5s` - Runtime per job
- `UPDATE_CHECK=1` - Update check enabled

## Clean Code Principles

Follow these clean code principles when contributing:

### Single Responsibility
- Each function should do one thing and do it well
- Keep functions small and focused (ideally < 30 lines)
- Separate concerns: parsing, validation, execution, output

### Meaningful Names
- Use descriptive function names: `validate_size_string` not `check`
- Use consistent naming conventions (snake_case for functions/variables)
- Prefix validation functions with `validate_`
- Prefix parsing functions with `parse_`

### DRY (Don't Repeat Yourself)
- Extract common patterns into reusable functions
- Use helper functions for repeated validation logic
- Centralize error handling and output formatting

### Comments and Documentation
- Functions should be self-documenting through clear names
- Add comments only when explaining "why", not "what"
- Keep help text and documentation in sync with code

### Error Handling
- Fail fast with clear error messages
- Validate inputs early before processing
- Use consistent exit codes (0=success, 1=error)

### Code Organization
- Group related functions together
- Order: constants → helpers → validators → core logic → main
- Keep configuration separate from logic

## Shell Script Best Practices

- Use `set -e` to exit on errors
- Quote variables: `"$VAR"` not `$VAR`
- Use `[[` for conditionals (bash)
- Prefer `local` variables in functions
- Use meaningful return codes
- Avoid global state when possible

## Testing Guidelines

- All features should have corresponding tests in `.github/workflows/tests.yml`
- Test both valid and invalid inputs
- Test CLI arguments in all formats: `--key value`, `--key=value`, `-k value`
- Use dry-run mode for input validation tests
- Use minimal sizes/runtimes for actual benchmark tests

## Docker Best Practices

- Keep the container minimal (scratch-based)
- Only include necessary binaries
- Use multi-stage builds
- Set appropriate defaults via ENV
- Run as non-root user (65534:65534)

## CI/CD Workflows

- `tests.yml` - Input validation and benchmark tests
- `docker-image.yml` - Build and push to GHCR (always) and Docker Hub (tags only)
- `codeql.yml` - Security scanning

## Output Formats

The tool supports multiple output formats:
- Human-readable (default): colored, with emojis
- JSON: structured, machine-readable
- YAML: structured, human-friendly
- XML: structured, enterprise-compatible

When modifying output, ensure all formats are updated consistently.
107 changes: 80 additions & 27 deletions .github/workflows/docker-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,88 +17,141 @@ permissions:
contents: read
packages: write
pull-requests: write
security-events: write

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

jobs:
build:
name: Build and Push Docker Image
runs-on: ubuntu-latest
env:
HAS_DOCKERHUB_SECRETS: ${{ github.event_name != 'pull_request' || github.repository == github.event.pull_request.head.repo.full_name }}
IS_TAG: ${{ startsWith(github.ref, 'refs/tags/') }}
SHOULD_PUSH: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3.4.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.9.0
- name: Login to Docker Hub
if: ${{ env.HAS_DOCKERHUB_SECRETS }}
uses: docker/login-action@v3
if: ${{ env.IS_TAG }}
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5.6.1
with:
images: |
name=${{ vars.GHCR_IMAGE }}
name=${{ vars.DOCKERHUB_IMAGE }},enable=${{ startsWith(github.ref, 'refs/tags/') }}
labels: |
org.opencontainers.image.title=docker-diskmark
org.opencontainers.image.description=A disk benchmarking tool for Docker
org.opencontainers.image.revision=${{ env.SHA }}
org.opencontainers.image.revision=${{ github.sha }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=ref,event=pr
type=raw,value=latest,enable={{is_default_branch}}
type=sha,format=long,prefix=sha-
- name: Determine version
id: version
run: |
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
echo "version=${{ github.ref_name }}" >> $GITHUB_OUTPUT
VERSION="${{ github.ref_name }}"
VERSION="${VERSION#v}"
else
echo "version=${{ github.sha }}" >> $GITHUB_OUTPUT
GIT_DESC=$(git describe --tags --always 2>/dev/null)
if [[ "$GIT_DESC" =~ ^v?([0-9]+\.[0-9]+\.[0-9]+)-([0-9]+)-g([a-f0-9]+)$ ]]; then
VERSION="${BASH_REMATCH[1]}-dev.${BASH_REMATCH[2]}+${BASH_REMATCH[3]}"
else
VERSION="0.0.0-dev+${GITHUB_SHA}"
fi
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Build and push Docker image
uses: docker/build-push-action@v5
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v5.4.0
with:
context: .
platforms: ${{ env.PLATFORMS }}
pull: true
cache-from: type=gha
cache-to: type=gha
push: true
push: ${{ env.SHOULD_PUSH }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
VERSION=${{ steps.version.outputs.version }}
- name: Docker Scout
id: docker-scout
if: ${{ github.event_name == 'pull_request' }}
uses: docker/scout-action@v1
with:
command: cves,compare
image: ${{ steps.meta.outputs.tags }}
to: ${{ vars.GHCR_IMAGE }}:latest
ignore-unchanged: true
only-fixed: true
write-comment: true
github-token: ${{ secrets.GITHUB_TOKEN }}

update-description:
name: Update DockerHub Description
runs-on: ubuntu-latest
needs: build
if: ${{ github.ref == 'refs/heads/main' }}
steps:
- name: Update repo description
if: ${{ github.ref == 'refs/heads/main' && env.HAS_DOCKERHUB_SECRETS }}
uses: peter-evans/dockerhub-description@v4
uses: peter-evans/dockerhub-description@e98e4d1628a5f3be2be7c231e50981aee98723ae # v4.0.2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
repository: ${{ vars.DOCKERHUB_IMAGE }}

scan:
name: Security Scan (${{ matrix.scanner }})
runs-on: ubuntu-latest
needs: build
strategy:
fail-fast: false
matrix:
scanner: [trivy, grype]
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Login to GitHub Container Registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}

# Trivy
- name: Run Trivy vulnerability scanner
if: ${{ matrix.scanner == 'trivy' }}
uses: aquasecurity/trivy-action@18f2510ee396bbf400402947b394f2dd8c87dbb0 # 0.30.0
with:
image-ref: ${{ vars.GHCR_IMAGE }}:sha-${{ github.sha }}
format: sarif
output: trivy-results.sarif
severity: CRITICAL,HIGH,MEDIUM
- name: Upload Trivy scan results
if: ${{ matrix.scanner == 'trivy' && always() }}
uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.1
with:
sarif_file: trivy-results.sarif

# Grype
- name: Run Grype vulnerability scanner
if: ${{ matrix.scanner == 'grype' }}
id: grype
uses: anchore/scan-action@abae793926ec39a78ab18002bc7fc45bbbd94342 # v6.0.0
with:
image: ${{ vars.GHCR_IMAGE }}:sha-${{ github.sha }}
fail-build: false
severity-cutoff: medium
- name: Upload Grype scan results
if: ${{ matrix.scanner == 'grype' && always() }}
uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.1
with:
sarif_file: ${{ steps.grype.outputs.sarif }}
Loading
Loading