Thank you for your interest in contributing to Ofelia! This document provides guidelines for contributing to the project.
- Developer Certificate of Origin (DCO)
- Development Setup
- Testing Strategy
- Code Style
- Pull Request Process
- Release Process
All contributions to Ofelia must include a Signed-off-by trailer in the
commit message, certifying that you wrote the code or have the right to submit
it under the project's open-source license. This is the
Developer Certificate of Origin (DCO).
Add --signoff (or -s) to your git commit command:
git commit --signoff -m "feat(core): add new scheduler algorithm"This appends a line like:
Signed-off-by: Your Name <your.email@example.com>
If you have already made commits without sign-off, you can amend or rebase to add it:
# Amend the last commit
git commit --amend --signoff --no-edit
# Rebase and sign off all commits on your branch
git rebase --signoff HEAD~N # where N is the number of commitsThe project's lefthook commit-msg hook validates the presence of
Signed-off-by locally. CI also checks all commits in a pull request via the
DCO GitHub App.
- Go 1.25 or higher
- Docker (for integration and E2E tests)
- Docker Swarm enabled (for service job tests)
go build -o ofelia ../ofelia daemon --config config.iniOfelia uses a multi-layered testing approach following the testing pyramid:
E2E Tests (e2e/)
Integration Tests (integration tag)
Unit Tests (no build tags)
Location: core/*_test.go (files without build tags)
Coverage Target: 65%+
Run Command: go test -v ./core/
Unit tests verify individual components in isolation using mocks where needed. They should:
- Be fast (<100ms per test)
- Not require external dependencies (Docker, network, etc.)
- Use mocks for Docker client when testing job logic
- Focus on business logic, error handling, and edge cases
Example:
# Run unit tests only
go test -v ./core/
# Run with coverage
go test -v -coverprofile=coverage.out ./core/
go tool cover -func=coverage.outLocation: core/*_test.go (files with //go:build integration tag)
Coverage Target: Critical paths for Docker integration
Run Command: go test -tags=integration -v ./core/
Integration tests verify interaction with real Docker daemon. They should:
- Require Docker daemon running
- Use real containers (alpine:latest for simplicity)
- Test actual Docker API behavior
- Clean up containers after test completion
Swarm Requirements: Some tests require Docker Swarm to be initialized:
docker swarm init
go test -tags=integration -v ./core/Example:
# Run integration tests
go test -tags=integration -v ./core/
# Run specific integration test
go test -tags=integration -v -run TestExecJob_WorkingDir_Integration ./core/Location: e2e/ directory (files with //go:build e2e tag)
Coverage Target: Complete system behavior scenarios
Run Command: go test -tags=e2e -v ./e2e/
E2E tests verify complete Ofelia system behavior with actual containers and scheduler. They should:
- Test scheduler lifecycle (start, execute, stop)
- Verify concurrent job execution
- Validate failure resilience
- Use real Docker containers and scheduler instances
- Take longer to run (5-30 seconds per test)
Example:
# Run all E2E tests
go test -tags=e2e -v ./e2e/
# Run specific E2E test
go test -tags=e2e -v -run TestScheduler_BasicLifecycle ./e2e/
# Run with timeout for long-running tests
go test -tags=e2e -v -timeout 5m ./e2e/# Unit tests only (fast, no Docker required)
go test -v ./...
# Unit + Integration tests (requires Docker)
go test -tags=integration -v ./...
# All tests including E2E (requires Docker)
go test -tags=e2e,integration -v ./...View coverage report:
# Generate coverage for unit tests
go test -coverprofile=coverage.out ./core/
go tool cover -html=coverage.out
# Generate coverage including integration tests
go test -tags=integration -coverprofile=coverage-integration.out ./core/
go tool cover -html=coverage-integration.outCoverage Goals:
- Core package unit tests: 65%+
- Core package with integration tests: 70%+
- Focus on error paths and edge cases
- 100% coverage not required for test helpers
func TestJobNameValidation(t *testing.T) {
job := &ExecJob{}
job.Name = ""
if err := job.Validate(); err == nil {
t.Error("Expected error for empty job name")
}
}//go:build integration
// +build integration
package core
func TestExecJob_RealDocker_Integration(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
client, err := docker.NewClient("unix:///var/run/docker.sock")
if err != nil {
t.Skip("Docker not available, skipping integration test")
}
// ... test implementation with real Docker
}//go:build e2e
// +build e2e
package e2e
func TestScheduler_NewFeature(t *testing.T) {
// 1. Setup: Create test containers
// 2. Configure: Create jobs and scheduler
// 3. Execute: Start scheduler and let jobs run
// 4. Verify: Check execution history and results
// 5. Cleanup: Stop scheduler and remove containers
}- Use descriptive test names:
TestExecJob_WorkingDir_UsesContainerDefault - Clean up resources: Always use
deferfor cleanup (containers, files) - Skip when dependencies unavailable: Check Docker availability, skip if not present
- Avoid flaky tests: Don't rely on precise timing, use synchronization primitives
- Test error paths: Don't just test happy paths, verify error handling
- Keep tests focused: One test should verify one behavior
- Use table-driven tests: For testing multiple similar scenarios
Tests run automatically on:
- Pull requests (unit tests)
- Main branch commits (unit + integration tests)
- Release tags (all tests including E2E)
- Follow standard Go conventions (
gofmt,golint) - Use meaningful variable and function names
- Add comments for exported functions and types
- Keep functions focused and small
- Handle errors explicitly, don't ignore them
- Always use absolute container IDs, not names (names can conflict)
- Clean up containers in
deferstatements - Use
alpine:latestfor test containers (small, fast) - Check container status before operations
// Good: Explicit error handling
if err := job.Run(ctx); err != nil {
return fmt.Errorf("job run: %w", err)
}
// Bad: Ignoring errors
job.Run(ctx)- Fork and create a branch: Create a feature branch from
main - Write tests: Add tests for new functionality (unit + integration if needed)
- Run tests: Verify all tests pass locally
- Update documentation: Update README.md, docs/, or CONTRIBUTING.md as needed
- Create PR: Write clear description of changes and why they're needed
- Address feedback: Respond to review comments and make requested changes
- All commits are signed off (
git commit --signoff) per DCO - Tests added for new functionality
- All tests pass (
go test -tags=integration,e2e -v ./...) - Code follows project style guidelines
- Documentation updated (if needed)
- Commit messages are clear and descriptive
- No breaking changes (or clearly documented if unavoidable)
Ofelia uses SLSA Level 3 provenance for all releases, providing cryptographic guarantees about the build process.
Version is automatically derived from the git tag by GoReleaser.
-
Create signed tag (recommended for GPG-signed releases):
# Configure GPG signing (one-time setup) git config --global user.signingkey YOUR_GPG_KEY_ID git config --global tag.gpgSign true # Create signed tag git tag -s v0.X.Y -m "Release v0.X.Y" git push origin v0.X.Y
-
Create GitHub Release: Go to Releases → Draft a new release
- Select the tag you just created
- Generate release notes or write a summary
- Publish the release
-
Automated pipeline: The release workflow automatically:
- Builds binaries with SLSA Level 3 provenance
- Generates SBOMs for all artifacts
- Creates signed checksums (Cosign keyless)
- Builds and signs container images
- Updates release notes with verification instructions
All releases include:
| Artifact | Verification |
|---|---|
| Binaries | SLSA L3 provenance attestation |
| Checksums | Cosign signature + certificate |
| Containers | Cosign signature + SBOM |
| Source | Signed git tag (if created with -s) |
Users can verify releases:
# Verify binary provenance
slsa-verifier verify-artifact ofelia-linux-amd64 \
--provenance-path ofelia-linux-amd64.intoto.jsonl \
--source-uri github.com/netresearch/ofelia
# Verify checksums signature
cosign verify-blob \
--certificate checksums.txt.pem \
--signature checksums.txt.sig \
--certificate-identity "https://github.com/netresearch/ofelia/.github/workflows/release-slsa.yml@refs/tags/vX.Y.Z" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
checksums.txt- Open an issue for bugs or feature requests
- Discuss major changes before implementing
- Ask questions in pull request comments
Thank you for contributing to Ofelia!