- Problem Statement
- CI/CD Pipeline Architecture
- Local Validation Script
- Known Issues & Fixes
- Usage Guide
- Integration with Git Hooks
- Troubleshooting
Our current local validation script (scripts/validate-all.sh) was designed for the original repository structure and checks these components:
api/agent/monitoring-agent/ui/
However, our CI/CD pipeline (.github/workflows/pipeline-v2.yml) now tests a completely different set of 6 components:
api(viacd api && ...)org-management-controller(via./pkg/controller/org-management/...)probe-controller(via./pkg/probe/...)ingestor(via./pkg/ingestor/...)customer-probe-agent(via./cmd/customer-probe-agent/...)pkg(via./pkg/...)
This mismatch means:
- ❌ Issues that will fail CI/CD cannot be detected locally
- ❌ Developers push code thinking it's validated
- ❌ CI/CD fails 15-30 minutes later
- ❌ Developer context-switches back to fix the issue
- ❌ Another push, another 15-30 minute wait
- ❌ Total time wasted per issue: 30-60 minutes
Create a new validation script (validate-pipeline-local.sh) that:
- ✅ Mirrors the exact CI/CD pipeline steps
- ✅ Tests all 6 components the CI/CD tests
- ✅ Runs the same commands in the same order
- ✅ Provides immediate feedback (5-10 minutes vs 15-30 minutes)
- ✅ Detects 100% of CI/CD issues before pushing
Location: .github/workflows/pipeline-v2.yml
The CI/CD pipeline uses GitHub Actions matrix strategy to test 6 components in parallel:
strategy:
matrix:
component:
- api
- org-management-controller
- probe-controller
- ingestor
- customer-probe-agent
- pkg
fail-fast: false
max-parallel: 6Every component goes through these 5 validation steps:
Ensures all Go code is properly formatted using gofmt.
Per-component commands:
# api
test -z "$(find api -name '*.go' -not -path '*/vendor/*' -exec gofmt -l {} +)"
# pkg
test -z "$(find pkg -name '*.go' -not -path '*/vendor/*' -exec gofmt -l {} +)"
# org-management-controller
test -z "$(find pkg/controller/org-management -name '*.go' -not -path '*/vendor/*' -exec gofmt -l {} +)"
# probe-controller
test -z "$(find pkg/probe -name '*.go' -not -path '*/vendor/*' -exec gofmt -l {} +)"
# ingestor
test -z "$(find pkg/ingestor -name '*.go' -not -path '*/vendor/*' -exec gofmt -l {} +)"
# customer-probe-agent
test -z "$(find cmd/customer-probe-agent -name '*.go' -not -path '*/vendor/*' -exec gofmt -l {} +)"Exit code: 0 = pass, 1 = files need formatting
Examines Go source code and reports suspicious constructs.
Per-component commands:
# api
cd api && go vet ./...
# pkg
go vet ./pkg/...
# org-management-controller
go vet ./pkg/controller/org-management/...
# probe-controller
go vet ./pkg/probe/...
# ingestor
go vet ./pkg/ingestor/...
# customer-probe-agent (HAS BUG - see Known Issues)
go vet ./cmd/customer-probe-agent/... # ❌ BROKEN - should cd firstStaticcheck is a state-of-the-art linter for Go. It uses static analysis to find bugs and performance issues.
Installation (done in CI/CD):
go install honnef.co/go/tools/cmd/staticcheck@latestPer-component commands:
# api
cd api && staticcheck ./...
# pkg
staticcheck ./pkg/...
# org-management-controller
staticcheck ./pkg/controller/org-management/...
# probe-controller
staticcheck ./pkg/probe/...
# ingestor
staticcheck ./pkg/ingestor/...
# customer-probe-agent (HAS BUG - see Known Issues)
staticcheck ./cmd/customer-probe-agent/... # ❌ BROKEN - should cd firstSuppression syntax:
//lint:ignore SA1019 Reason for ignoring this specific check
return deprecatedFunction()
//lint:file-ignore SA1019 AWS SDK deprecations - planned migration
package servicesInspects source code for security problems.
Installation (done in CI/CD):
go install github.com/securego/gosec/v2/cmd/gosec@latestPer-component commands:
# api
cd api && gosec -quiet ./...
# pkg
gosec -quiet ./pkg/...
# org-management-controller
gosec -quiet ./pkg/controller/org-management/...
# probe-controller
gosec -quiet ./pkg/probe/...
# ingestor
gosec -quiet ./pkg/ingestor/...
# customer-probe-agent (HAS BUG - see Known Issues)
gosec -quiet ./cmd/customer-probe-agent/... # ❌ BROKEN - should cd firstSuppression syntax:
// #nosec G115 - queueSize is always positive, validated at creation
queueCapacity := uint64(pq.queueSize)Runs all unit tests with race detector and generates coverage reports.
Per-component commands:
# api
cd api && go test -v -race -coverprofile=coverage.out -covermode=atomic ./...
# org-management-controller
go test -v -race -coverprofile=coverage.out -covermode=atomic ./pkg/controller/org-management/...
# probe-controller
go test -v -race -coverprofile=coverage.out -covermode=atomic ./pkg/probe/...
# ingestor
go test -v -race -coverprofile=coverage.out -covermode=atomic ./pkg/ingestor/...
# customer-probe-agent
cd cmd/customer-probe-agent && go test -v -race -coverprofile=coverage.out -covermode=atomic ./...
# pkg
go test -v -race -coverprofile=coverage.out -covermode=atomic ./pkg/...Flags explained:
-v: Verbose output-race: Enable race detector (catches data races)-coverprofile=coverage.out: Generate coverage report-covermode=atomic: Coverage mode for race detector compatibility
File: scripts/validate-pipeline-local.sh
-
Exact Parity with CI/CD
- Uses identical commands
- Same execution order
- Same exit codes
-
Clear Output
- Component-by-component progress
- ✓/✗ indicators for each step
- Color-coded results
-
Detailed Logging
- Saves full output to
.validation-logs/ - Timestamped log files
- Easy debugging
- Saves full output to
-
Fast Failure
- Stops on first error per component (default)
--continueflag to validate all components
-
Component Isolation
- Each component tested independently
- Can validate single component with
--component
#!/bin/bash
# Validates all 6 CI/CD components locally before pushing
set -e
# Color codes
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Components matching CI/CD pipeline
COMPONENTS=(
"api"
"org-management-controller"
"probe-controller"
"ingestor"
"customer-probe-agent"
"pkg"
)
# Validation functions
validate_go_fmt() { ... }
validate_go_vet() { ... }
validate_staticcheck() { ... }
validate_gosec() { ... }
validate_tests() { ... }
# Main validation loop
for component in "${COMPONENTS[@]}"; do
echo "Validating $component..."
validate_go_fmt "$component"
validate_go_vet "$component"
validate_staticcheck "$component"
validate_gosec "$component"
validate_tests "$component"
done🔍 Local CI/CD Validation
═══════════════════════════════════════════════════════════════
[1/6] Validating api...
✓ go fmt check
✓ go vet
✓ staticcheck
✓ gosec
✓ tests (47 passed)
[2/6] Validating org-management-controller...
✓ go fmt check
✓ go vet
✓ staticcheck
✓ gosec
✓ tests (23 passed)
...
═══════════════════════════════════════════════════════════════
✅ All 6 components passed validation!
Logs saved to: .validation-logs/20251011_123045/
Problem: customer-probe-agent has its own go.mod file (separate Go module), but the CI/CD workflow runs validation commands from the repository root.
Symptom:
$ go vet ./cmd/customer-probe-agent/...
pattern ./cmd/customer-probe-agent/...: directory prefix cmd/customer-probe-agent
does not contain main module or its selected dependenciesRoot Cause: When a directory has its own go.mod, it becomes a separate module. Commands like go vet ./cmd/customer-probe-agent/... from the root try to vet it as part of the root module, which fails.
Fix in Workflow (.github/workflows/pipeline-v2.yml):
# BEFORE (BROKEN):
- name: Run go vet
run: |
case "${{ matrix.component }}" in
customer-probe-agent)
go vet ./cmd/customer-probe-agent/... # ❌ Fails
;;
esac
# AFTER (FIXED):
- name: Run go vet
run: |
case "${{ matrix.component }}" in
customer-probe-agent)
cd cmd/customer-probe-agent && go vet ./... # ✅ Works
;;
esacApply same fix to:
staticcheckstep (lines 148-150)gosecstep (lines 172-174)
Tests are already correct: They already cd into the directory before running.
Problem: Some fields use uint for organization IDs but NATS functions expect uint64.
Symptom:
// pkg/ingestor/server/server.go:331
if uint64(conn.OrganizationID) != organizationIDThe fact that we need uint64() cast suggests a type mismatch.
Investigation:
# Find all OrganizationID field definitions
grep -r "OrganizationID.*uint" pkg/ingestor/server/
# Check if it should be uint64 everywhere
grep -r "func.*organizationID uint64" pkg/Fix: Ensure OrganizationID is consistently uint64 throughout ingestor package.
Problem: Staticcheck sometimes reports false positives for format strings.
Symptom:
pkg/notification/analytics/reports.go:392:21: couldn't parse format string (SA5009)
Fix: Use //lint:ignore SA5009 to suppress:
//lint:ignore SA5009 staticcheck false positive - format string is valid with %% for literal %
return fmt.Sprintf(html, ...)File-level suppression:
//lint:file-ignore SA1019 AWS SDK deprecations - planned migration
package services./scripts/validate-pipeline-local.shThis is the recommended command before every push. It validates all 6 components exactly as CI/CD will.
Time: ~5-10 minutes (depends on test count)
./scripts/validate-pipeline-local.sh --component ingestorUse this when you've only changed code in one component and want quick feedback.
Time: ~1-2 minutes per component
./scripts/validate-pipeline-local.sh --quickSkips expensive operations like full test suites. Only runs fmt, vet, staticcheck, gosec.
Time: ~2-3 minutes
When to use: Pre-commit checks, quick verification
./scripts/validate-pipeline-local.sh --continueValidates all components even if some fail. Useful for getting complete picture of issues.
Default behavior: Stops at first failure per component
# =============================================================================
# Local CI/CD Validation
# =============================================================================
.PHONY: validate-pipeline-local validate-component
# Validate all components (mirrors CI/CD pipeline)
validate-pipeline-local:
@echo "Running full pipeline validation locally..."
@./scripts/validate-pipeline-local.sh
# Validate single component
validate-component:
@echo "Validating component: $(COMPONENT)..."
@./scripts/validate-pipeline-local.sh --component $(COMPONENT)
# Quick validation (skip expensive tests)
validate-quick:
@echo "Running quick validation..."
@./scripts/validate-pipeline-local.sh --quick# Full validation
make validate-pipeline-local
# Single component
make validate-component COMPONENT=ingestor
# Quick mode
make validate-quickLogs are saved to .validation-logs/TIMESTAMP/ with this structure:
.validation-logs/
└── 20251011_123045/
├── api_fmt.log
├── api_vet.log
├── api_staticcheck.log
├── api_gosec.log
├── api_tests.log
├── ingestor_fmt.log
├── ingestor_vet.log
└── ...
To view a specific failure:
# List recent validation runs
ls -lt .validation-logs/
# View specific log
cat .validation-logs/20251011_123045/ingestor_staticcheck.log
# Search for errors
grep -r "error:" .validation-logs/20251011_123045/Log cleanup: Script automatically keeps last 10 runs, deletes older logs.
File: .githooks/pre-push
#!/bin/bash
# Validate code before pushing to remote
echo "🔍 Running local CI/CD validation before push..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# Run quick validation (skip expensive full test suites)
./scripts/validate-pipeline-local.sh --quick
# Check exit code
if [ $? -ne 0 ]; then
echo ""
echo "❌ Validation failed! Push aborted."
echo ""
echo "To fix:"
echo " 1. Review errors above"
echo " 2. Fix issues and commit"
echo " 3. Push again"
echo ""
echo "To skip validation (NOT recommended):"
echo " git push --no-verify"
exit 1
fi
echo ""
echo "✅ Validation passed! Proceeding with push..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
exit 0# Configure git to use .githooks/ directory
git config core.hooksPath .githooks
# Make hook executable
chmod +x .githooks/pre-pushIf you absolutely must push without validation (production emergency, etc.):
git push --no-verifyCause: staticcheck not installed
Fix:
go install honnef.co/go/tools/cmd/staticcheck@latestCause: gosec not installed
Fix:
go install github.com/securego/gosec/v2/cmd/gosec@latestCause: Some tests take longer than default timeout
Fix: Increase timeout in test command:
go test -v -race -timeout=10m -coverprofile=coverage.out ./...Cause: Script not executable
Fix:
chmod +x scripts/validate-pipeline-local.shCause: Workflow bug - see Known Issues
Temporary Workaround: Skip customer-probe-agent until workflow fixed:
./scripts/validate-pipeline-local.sh --skip customer-probe-agentCause: Many validation runs, logs not cleaned up
Fix:
# Manual cleanup (keeps last 10 runs)
ls -t .validation-logs | tail -n +11 | xargs -I {} rm -rf .validation-logs/{}
# Or delete all logs
rm -rf .validation-logsPossible causes:
-
Go version mismatch: CI/CD uses Go 1.24 - check yours:
go version # Should be 1.24.x -
Stale dependencies: Update go.mod:
go mod tidy go mod download
-
Uncommitted changes: CI/CD tests committed code:
git status # Check for uncommitted changes -
Cache issues: Clear Go cache:
go clean -cache -testcache -modcache
Solutions:
-
Use quick mode for pre-commit:
./scripts/validate-pipeline-local.sh --quick
-
Validate only changed components:
# If you only changed ingestor ./scripts/validate-pipeline-local.sh --component ingestor -
Run in parallel (future enhancement):
./scripts/validate-pipeline-local.sh --parallel
| Component | fmt | vet | staticcheck | gosec | tests | Path |
|---|---|---|---|---|---|---|
| api | ✓ | ✓ | ✓ | ✓ | ✓ | api/ |
| org-management-controller | ✓ | ✓ | ✓ | ✓ | ✓ | pkg/controller/org-management/ |
| probe-controller | ✓ | ✓ | ✓ | ✓ | ✓ | pkg/probe/ |
| ingestor | ✓ | ✓ | ✓ | ✓ | ✓ | pkg/ingestor/ |
| customer-probe-agent | ✓ | ✓ | cmd/customer-probe-agent/ |
|||
| pkg | ✓ | ✓ | ✓ | ✓ | ✓ | pkg/ |
cd before command
- CI/CD Standardization Summary
- Makefile Pipeline Quick Reference
- Task Execution Workflow
- Error Response Standard
Last Updated: 2025-10-11 Maintained By: Platform Team Questions: See #engineering-platform on Slack