|
| 1 | +# Copilot Instructions for os-image-composer |
| 2 | + |
| 3 | +## Architecture Overview |
| 4 | + |
| 5 | +OS Image Composer builds custom Linux images from pre-built packages. Key components: |
| 6 | + |
| 7 | +- **Provider** (`internal/provider/`) - Orchestrates builds per OS (azl, elxr, emt, rcd, ubuntu). Implements `Provider` interface with `Name`, `Init`, `PreProcess`, `BuildImage`, `PostProcess` methods. Each provider exports an `OsName` constant and a `Register()` function |
| 8 | +- **Image makers** (`internal/image/`) - Creates output formats: `rawmaker/`, `isomaker/`, `initrdmaker/` |
| 9 | +- **Chroot** (`internal/chroot/`) - Isolated build environments with package installers for `deb/` and `rpm/` |
| 10 | +- **Config** (`internal/config/`) - Template loading, merging defaults with user templates, validation |
| 11 | +- **OsPackage** (`internal/ospackage/`) - Package utilities: `debutils/`, `rpmutils/` for dependency resolution |
| 12 | + |
| 13 | +Data flow: CLI → Config loads template → Provider.Init → Provider.PreProcess (downloads packages) → Provider.BuildImage (creates chroot, installs packages, generates image) → Provider.PostProcess |
| 14 | + |
| 15 | +## Build and Test |
| 16 | + |
| 17 | +Always use **Earthly** for builds and testing when available. If Earthly is not configured in your environment, you can fall back to standard Go commands: |
| 18 | + |
| 19 | +| Task | Earthly (preferred) | Go fallback | |
| 20 | +|------|---------------------|-------------| |
| 21 | +| Build | `earthly +build` | `go build ./...` | |
| 22 | +| Test (fast) | `earthly +test-quick` | `go test ./...` | |
| 23 | +| Test (coverage) | `earthly +test` | `go test -coverprofile=coverage.out ./...` | |
| 24 | +| Lint | `earthly +lint` | `golangci-lint run` | |
| 25 | + |
| 26 | +Note: CI runs Earthly, so always verify with `earthly +test` and `earthly +lint` before opening a PR if possible, to avoid CI failures. |
| 27 | + |
| 28 | +Coverage threshold is enforced in CI and auto-ratcheted — see `.coverage-threshold` for the current value |
| 29 | + |
| 30 | +## Adding a New OS Provider |
| 31 | + |
| 32 | +1. Create package in `internal/provider/{osname}/` |
| 33 | +2. Implement `provider.Provider` interface (see `internal/provider/provider.go`) |
| 34 | +3. Register in `cmd/os-image-composer/build.go` switch statement |
| 35 | +4. Add default configs in `config/osv/{osname}/` |
| 36 | +5. Create example templates in `image-templates/` |
| 37 | + |
| 38 | +## Testing Patterns |
| 39 | + |
| 40 | +- Use **stdlib `testing` only** — no testify or external assertion libraries |
| 41 | +- Use **table-driven tests** with `t.Run()` for multiple cases: |
| 42 | +```go |
| 43 | +tests := []struct{ name string; input string; want error }{...} |
| 44 | +for _, tt := range tests { |
| 45 | + t.Run(tt.name, func(t *testing.T) { ... }) |
| 46 | +} |
| 47 | +``` |
| 48 | +- Follow the **AAA pattern**: Arrange (setup), Act (execute), Assert (verify) |
| 49 | +- Test file naming: `*_test.go` in same package |
| 50 | +- Reset shared state in tests (see `resetBuildFlags()` pattern in `cmd/os-image-composer/build_test.go`) |
| 51 | + |
| 52 | +## Error Handling |
| 53 | + |
| 54 | +- **Always wrap** with context: `fmt.Errorf("failed to X: %w", err)` |
| 55 | +- Use named returns with defer for cleanup (see `docs/architecture/os-image-composer-coding-style.md`) |
| 56 | +- Never ignore errors with `_` |
| 57 | + |
| 58 | +## Logging |
| 59 | + |
| 60 | +- Use the project logger, **not** `fmt.Println` or `log` stdlib: |
| 61 | +```go |
| 62 | +import "github.com/open-edge-platform/os-image-composer/internal/utils/logger" |
| 63 | + |
| 64 | +var log = logger.Logger() |
| 65 | +``` |
| 66 | +- Every package that logs should declare `var log = logger.Logger()` at package level |
| 67 | +- **Three-layer logging strategy**: |
| 68 | + - **Utilities** (`internal/utils/`): primarily return errors; use `log.Debugf()` sparingly |
| 69 | + - **Business logic** (`internal/provider/`, `internal/config/`, etc.): log with business context (`log.Infof`, `log.Warnf`, `log.Errorf`) and return errors with technical context |
| 70 | + - **Top-level orchestrators** (`cmd/`): only return errors — logging happens at lower levels |
| 71 | +- Available levels: `log.Debugf()`, `log.Infof()`, `log.Warnf()`, `log.Errorf()` |
| 72 | + |
| 73 | +## Code Style |
| 74 | + |
| 75 | +- **Line length**: 120 characters max |
| 76 | +- **Function length**: under 50 lines — break up larger functions |
| 77 | +- **Parameters**: max 4–5 per function; use a config/options struct beyond that |
| 78 | +- **Imports**: stdlib → third-party → local (blank line separated) |
| 79 | +- **Struct-based design over globals** — prefer dependency injection |
| 80 | +- **Interface naming**: should end with `-er` when possible (e.g., `PackageInstaller`, `ConfigReader`) |
| 81 | +- **Named returns + defer for cleanup** — the standard cleanup pattern (not "goto fail"); see [coding style Section 4.3](docs/architecture/os-image-composer-coding-style.md) |
| 82 | +- **Linters** (`earthly +lint`): `govet`, `gofmt`, `errcheck`, `staticcheck`, `unused`, `gosimple` — all errors must be handled (`errcheck` is enforced) |
| 83 | +- Shell scripts: `set -euo pipefail` |
| 84 | +- See `docs/architecture/os-image-composer-coding-style.md` for the full guide |
| 85 | + |
| 86 | +## Security |
| 87 | + |
| 88 | +- **HTTP clients**: Always use `network.NewSecureHTTPClient()` or the singleton `network.GetSecureHTTPClient()` from `internal/utils/network/` — enforces TLS 1.2+ with approved cipher suites. Never use `http.DefaultClient` |
| 89 | +- **Command execution**: Use the `internal/utils/shell/` package which maintains an allowlist of approved system commands. Never use raw `exec.Command()` |
| 90 | +- **Input validation**: Sanitize user-provided filenames and paths; use `filepath.Clean()` on paths |
| 91 | +- **Template validation**: Templates are validated against JSON schema (`os-image-template.schema.json`) via `os-image-composer validate` |
| 92 | +- **File permissions**: `0700` for chroot dirs, `0755` for general dirs, `0644` for data files, `0640` for log files |
| 93 | +- CI runs **Trivy** (dependency vulnerability scanning — fails on HIGH/CRITICAL), **Gitleaks** (secret detection), and **Zizmor** (GitHub Actions security auditing) |
| 94 | + |
| 95 | +## Documentation |
| 96 | + |
| 97 | +**Every PR that changes behavior must include corresponding documentation updates.** Documentation is a first-class deliverable — treat it as part of the code change, not an afterthought. |
| 98 | + |
| 99 | +Before opening a PR, check whether your changes affect any of these and update accordingly: |
| 100 | + |
| 101 | +| What changed | Docs to update | |
| 102 | +|---|---| |
| 103 | +| CLI flags or commands | `docs/architecture/os-image-composer-cli-specification.md`, `docs/tutorial/usage-guide.md` | |
| 104 | +| Build process or Earthfile targets | `docs/tutorial/usage-guide.md`, this file's **Build and Test** section | |
| 105 | +| Image template schema or fields | `docs/architecture/os-image-composer-templates.md`, relevant `image-templates/*.yml` examples | |
| 106 | +| New OS provider | `docs/architecture/architecture.md`, this file's **Adding a New OS Provider** section | |
| 107 | +| New tutorial-worthy feature | Add or update a guide in `docs/tutorial/` | |
| 108 | +| Architecture or design decisions | Add an ADR in `docs/architecture/` | |
| 109 | +| Security-related changes | `docs/architecture/image-composition-tool-security-objectives.md` | |
| 110 | +| Caching behavior | `docs/architecture/os-image-composer-caching.md` | |
| 111 | +| Coding conventions | `docs/architecture/os-image-composer-coding-style.md` | |
| 112 | +| Dependencies or multi-repo setup | `docs/architecture/os-image-composer-multi-repo-support.md` | |
| 113 | +| User-facing features or fixes | `docs/release-notes.md` | |
| 114 | + |
| 115 | +## Git Commits & PRs |
| 116 | + |
| 117 | +- Sign commits: `git commit -S` |
| 118 | +- Conventional commits: `type(scope): description` (feat, fix, docs, test, refactor, chore) |
| 119 | +- **Always use** `.github/PULL_REQUEST_TEMPLATE.md` for PRs |
| 120 | +- Branch prefixes: `feature/`, `fix/`, `docs/`, `refactor/` |
| 121 | +- **Always update documentation** alongside code changes (see **Documentation** section above). If no docs need updating, explicitly state so in the PR description |
| 122 | + |
| 123 | +## CI Quality Gates |
| 124 | + |
| 125 | +All PRs must pass these checks before merging: |
| 126 | + |
| 127 | +| Gate | What it checks | |
| 128 | +|------|----------------| |
| 129 | +| Unit tests + coverage | `earthly +test` — threshold auto-ratchets via `.coverage-threshold` | |
| 130 | +| Lint | `earthly +lint` — golangci-lint with `govet`, `gofmt`, `errcheck`, `staticcheck`, `unused`, `gosimple` | |
| 131 | +| Trivy scan | Dependency vulnerabilities (HIGH/CRITICAL) + SBOM generation | |
| 132 | +| Gitleaks | Secret leak detection in commits | |
| 133 | +| Zizmor | GitHub Actions workflow security auditing | |
| 134 | +| Integration builds | Per-OS/arch image builds | |
| 135 | + |
| 136 | +## Image Template Conventions |
| 137 | + |
| 138 | +- **Naming**: `<dist>-<arch>-<purpose>-<imageType>.yml` (e.g., `emt3-x86_64-minimal-raw.yml`) |
| 139 | +- **Minimal user templates**: only `image` + `target` sections required; OS defaults handle the rest |
| 140 | +- **Always include a `metadata` block** with `description`, `use_cases`, and `keywords` for discoverability |
| 141 | +- **Merge behavior**: packages are _additive_ (merged by name); `disk` configuration _replaces_ entirely |
| 142 | +- **Validate** before committing: `os-image-composer validate -t <template.yml>` |
| 143 | + |
| 144 | +## Key Files |
| 145 | + |
| 146 | +- `image-templates/*.yml` - Example image templates |
| 147 | +- `config/osv/` - OS-specific default configurations |
| 148 | +- `internal/config/config.go` - `ImageTemplate` struct definition |
| 149 | +- `internal/provider/provider.go` - Provider interface |
| 150 | +- `internal/utils/logger/` - Project logger (zap-based) |
| 151 | +- `internal/utils/network/securehttp.go` - Secure HTTP client |
| 152 | +- `internal/utils/shell/shell.go` - Command execution with allowlist |
| 153 | +- `.golangci.yml` - Linter configuration |
| 154 | +- `.coverage-threshold` - Current test coverage threshold |
| 155 | +- `docs/architecture/` - ADRs and design docs |
0 commit comments