-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture Overview
This document provides an overview of the GitLab CI Lint architecture.
GitLab CI Lint follows a modular, layered architecture with clear separation of concerns:
┌─────────────────────────────────────────────────────────────┐
│ CLI Layer │
│ (cmd/gitlab-ci-lint/) │
│ - Command parsing (Cobra) │
│ - Flag handling │
│ - Exit code management │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Configuration Layer │
│ (internal/config/) │
│ - Multi-source loading (defaults, file, env, flags) │
│ - Priority-based merging │
│ - Validation │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Validation Layer │
│ (internal/validator/) │
│ - Local YAML validation (validator/local.go) │
│ - API validation (validator/api.go) │
│ - Result aggregation │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Client Layer │
│ (internal/gitlab/) │
│ - HTTP client │
│ - GitLab API integration │
│ - Response parsing │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Output Layer │
│ (internal/output/) │
│ - Text formatting │
│ - JSON serialization │
│ - YAML serialization │
│ - Color handling │
└─────────────────────────────────────────────────────────────┘
Responsibility: User interaction and orchestration
Key Functions:
- Parse command-line arguments using Cobra
- Load configuration from all sources
- Coordinate validation pipeline
- Handle exit codes
Design Pattern: Command Pattern (Cobra)
Responsibility: Configuration management and loading
Components:
-
config.go: Configuration data structures
-
Config: Root configuration -
GitLabConfig: Instance settings -
AuthConfig: Authentication settings -
ValidationConfig: Validation behavior -
OutputConfig: Output formatting
-
-
loader.go: Multi-source configuration loading
- Implements priority chain: defaults → file → env → flags
- Merges configurations with proper precedence
- Validates final configuration
-
defaults.go: Default values
- Provides sensible defaults for all settings
- Allows operation without configuration
-
writer.go: Configuration persistence
- Atomic write operations
- Secure file permissions (0600)
- Directory creation
Design Pattern: Builder Pattern (for configuration loading)
Responsibility: CI configuration validation
Interface:
type Validator interface {
Validate(content []byte) Result
}Implementations:
-
Local Validator (
local.go):- YAML syntax validation using
gopkg.in/yaml.v3 - Line and column error reporting
- Error context preservation
- Configurable strict/lenient mode
- YAML syntax validation using
-
API Validator (
api.go):- GitLab API integration
- Job definition verification
- Semantic validation
- Warning reporting
Design Pattern: Strategy Pattern (Validator interface)
Responsibility: GitLab API communication
Components:
-
client.go: HTTP client
- Configurable timeout
- Token authentication
- URL normalization
- Error handling
-
models.go: API data structures
- Request/response types
- Error models
- Warning models
Endpoints:
- Global:
/api/v4/ci/lint - Project-specific:
/api/v4/projects/:id/ci/lint
Design Pattern: Facade Pattern (simplifies GitLab API)
Responsibility: Result formatting and presentation
Components:
-
formatter.go: Multi-format output
- Text: Human-readable, colorized
- JSON: Machine-readable
- YAML: Structured data
-
colors.go: Terminal color handling
- Auto-detection (TTY detection)
- Forced modes (always/never)
- ANSI color codes
Design Pattern: Strategy Pattern (format selection)
1. User executes: gitlab-ci-lint .gitlab-ci.yml
↓
2. Configuration loading (4 sources with priority)
↓
3. Read CI file content
↓
4. Stage 1: Local YAML validation
↓
[If invalid] → Print errors → Exit 10
↓
[If valid and API enabled]
↓
5. GitLab client creation
- Token validation (if provided)
- Connection test
↓
6. Stage 2: API validation
- Build endpoint (global or project-specific)
- Send request
- Parse response
↓
7. Result aggregation
- Combine local + API results
- Determine overall validity
↓
8. Output formatting
- Apply selected format
- Colorize if needed
↓
9. Exit with appropriate code (0, 1, or 10)
1. Load defaults (hardcoded)
↓
2. Load config file (if exists)
- Check GCL_CONFIG env var
- Check default path ~/.tools-config/.gitlab-ci-lint/config.yaml
- Check --config flag
↓
3. Merge defaults + config file
↓
4. Load environment variables (GCL_*)
↓
5. Merge with previous config
↓
6. Load CLI flags
↓
7. Merge with previous config
↓
8. Validate final config
- Check output format
- Check color mode
- Check instance URL
↓
9. Return final configuration
Decision: Use Validator interface instead of concrete types
Benefits:
- Easy to add new validation types
- Testable with mocks
- Clear separation of concerns
Example:
type Validator interface {
Validate(content []byte) Result
}
// Can easily add:
type SecurityValidator struct {}
type PerformanceValidator struct {}Decision: 4-level priority chain (defaults → file → env → flags)
Benefits:
- Flexible configuration
- No configuration required for basic use
- Environment variables for CI/CD
- Flags for one-time overrides
Precedence:
CLI Flags (highest)
↓
Environment Variables
↓
Config File
↓
Defaults (lowest)
Decision: Tool works with reduced capabilities if API unavailable
Benefits:
- Works offline (local-only validation)
- Faster feedback for syntax errors
- No API rate limit concerns
Implementation:
# Explicit skip
gitlab-ci-lint --skip-api .gitlab-ci.yml
# Fallback (if API unreachable)
# Still provides local validationDecision: All errors include context (line, column, content)
Benefits:
- Easy to locate issues
- IDE-friendly
- Better UX
Structure:
type Error struct {
Message string // Human-readable description
Line int // Line number
Column int // Column position
Content string // Actual line content
}// 1. Implement the interface
type CustomValidator struct {
// dependencies
}
func (v *CustomValidator) Validate(content []byte) validator.Result {
// validation logic
return validator.Result{...}
}
// 2. Wire up in main.go
customValidator := &CustomValidator{...}
result := customValidator.Validate(content)// 1. Add format function to formatter.go
func (f *Formatter) formatMarkdown(w io.Writer, results []validator.Result, filename string) {
// implementation
}
// 2. Update FormatResult switch
switch format {
case "markdown":
f.formatMarkdown(w, results, filename)
// ...
}// 1. Add loader in loader.go
func (l *Loader) loadFromDatabase() Config {
// implementation
}
// 2. Call in Load() before flags
cfg := l.loadFromDatabase()
cfg = l.mergeConfigs(cfg, flagCfg)- Tokens are never logged
- Config files use secure permissions (0600)
- Tokens are sent only over HTTPS
- All API requests use HTTPS
- Timeouts prevent hanging
- Token validation before use
- Atomic writes prevent corruption
- Config validation prevents injection
- No arbitrary code execution
- Static binary, ~15-20 MB
- In-memory file processing
- No persistent state
- YAML parsing: < 50ms for typical files
- API validation: 1-3 seconds (network bound)
- Single API request per validation
- Configurable timeout (default: 30s)
- No background processes
- Package-level testing
- Mock HTTP servers for API tests
- Table-driven test cases
- End-to-end validation flows
- Real GitLab instances (when available)
- Multi-format output verification
- Current: ~72% overall
- Target: 70%+
-
github.com/spf13/cobra: CLI framework -
gopkg.in/yaml.v3: YAML parsing - Standard library: HTTP, JSON, filesystem
-
semantic-release: Automated releases -
commitlint: Commit message linting -
husky: Git hooks
- Plugin System: Allow custom validators
- Caching: Cache API responses
- Parallel Validation: Validate multiple files concurrently
- Streaming: Process large files incrementally
- Web Interface: Browser-based validation
- Current design supports single-file validation
- Batch validation would require minimal changes
- API client is stateless (ready for scaling)