This guide covers building, testing, and contributing to AKS Flex Node.
For developers who want to build from source:
# Build the application
make build
# Build for all platforms (linux/amd64, linux/arm64)
make build-all
# Create release archives
make package-all
# Run tests
make test
# Run tests with coverage
make test-coverageFor a complete list of build targets, run make help.
- Operating System: Ubuntu 22.04 LTS, 24.04 LTS, or compatible Linux distribution
- Architecture: x86_64 (amd64) or arm64
- Go: Version 1.24 or higher
- Make: GNU Make
- Git: For version control
# Run all checks (format, vet, lint, test)
make check
# Format code and organize imports
make fmt-all
# Run linter
make lint
# Run go vet
make vet
# Verify and tidy dependencies
make verify# Run all tests
make test
# Run tests with race detection
make test-race
# Run tests with coverage report (opens coverage.html)
make test-coverage
# Run specific package tests
go test ./pkg/config/
go test ./pkg/logger/Before committing changes, ensure all checks pass:
make verify && make check && make build-allThe project uses GitHub Actions for automated testing on pull requests and pushes to main/dev branches. The testing infrastructure includes:
- Build verification across Go 1.24
- Unit tests with race detection and coverage reporting
- Code quality checks with multiple linters
- Security scanning with gosec
- Dependency review for vulnerabilities
The PR checks workflow (.github/workflows/pr-checks.yml) runs automatically on:
- Pull requests to
mainordevbranches - Direct pushes to
mainordevbranches
Jobs:
-
Build - Verifies the project builds successfully
- Tests on Go 1.24
- Builds for current platform and all supported platforms (linux/amd64, linux/arm64)
-
Test - Runs the test suite
- Executes all tests with race detection
- Generates coverage report
- Reports coverage percentage (warns if below 30% but doesn't fail)
-
Lint - Runs golangci-lint with comprehensive checks
- Uses
.golangci.ymlconfiguration - Checks code quality and common issues
- Uses
-
Security - Scans for security vulnerabilities
- Runs gosec security scanner
- Uploads results to GitHub Security tab
-
Code Quality - Additional quality checks
- Verifies code formatting with
gofmt - Verifies import formatting with
goimports - Runs
go vetfor correctness - Runs
staticcheckfor additional static analysis
- Verifies code formatting with
-
Dependency Review - Reviews dependencies for security issues
- Only runs on pull requests
- Fails on moderate or higher severity vulnerabilities
The project uses golangci-lint v2. If you don't have it installed:
# Linux/macOS (installs latest version)
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin
# macOS with Homebrew
brew install golangci-lint
# Or use Go install
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latestThe project uses .golangci.yml (v2 format) for linter configuration with the following enabled checks:
Enabled Linters:
- errcheck - Checks for unchecked errors (with
check-blank: falseto allow_ =in defer) - govet - Reports suspicious constructs
- ineffassign - Detects ineffectual assignments
- staticcheck - Advanced static analysis (includes gosimple checks)
- unused - Finds unused code
Exclusions:
- Test files (
_test.go) are excluded from errcheck to allow testing error conditions
The project enforces a minimum test coverage threshold of 30%. To view detailed coverage:
make test-coverage
# Opens coverage.html showing line-by-line coverageCoverage reports are uploaded as artifacts in GitHub Actions runs for review.
- Test files end with
_test.go - Place tests in the same package as the code being tested
- Use table-driven tests for multiple test cases
- Use subtests with
t.Run()for better organization
func TestFunctionName(t *testing.T) {
tests := []struct {
name string
input string
want string
wantErr bool
}{
{
name: "valid input",
input: "test",
want: "expected",
wantErr: false,
},
// more test cases...
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := FunctionName(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("FunctionName() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("FunctionName() = %v, want %v", got, tt.want)
}
})
}
}- Test behavior, not implementation - Focus on what the code does, not how
- Use meaningful test names - Describe what is being tested
- Keep tests simple - Each test should verify one thing
- Mock external dependencies - Use interfaces for testability
- Test edge cases - Include boundary conditions and error cases
- Use test fixtures - Keep test data organized and reusable
If tests fail in CI but pass locally:
- Check Go version matches CI (1.24)
- Run with race detector:
make test-race - Check for environment-specific issues
- Ensure dependencies are up to date:
make verify
If linter fails in CI but passes locally:
- Ensure golangci-lint version matches CI (latest)
- Run:
make lint - Check
.golangci.ymlfor configuration - Some issues may be platform-specific
If coverage drops below 30%:
- Add tests for new code
- Focus on critical paths first
- Review
coverage.htmlfor uncovered lines - Consider raising threshold as coverage improves
AKSFlexNode/
├── cmd/ # Command-line interface
├── pkg/
│ ├── auth/ # Azure authentication
│ ├── azure/ # Azure API interactions
│ ├── bootstrapper/ # Bootstrap orchestration
│ ├── components/ # Component installers
│ ├── config/ # Configuration management
│ ├── logger/ # Logging infrastructure
│ └── utils/ # Utility functions
├── scripts/ # Installation scripts
├── docs/ # Documentation
├── Makefile # Build and test targets
└── go.mod # Go module definition
- Follow standard Go conventions and idioms
- Use
gofmtfor code formatting - Pass
golangci-lintchecks - Write meaningful commit messages
- Add tests for new functionality
- Update documentation as needed
When adding new features:
- Create a feature branch from
main - Implement your changes with appropriate tests
- Ensure all checks pass:
make verify && make check - Update documentation if needed
- Submit a pull request with a clear description
If adding a new component to the bootstrap process:
- Create a new directory in
pkg/components/ - Implement the
Executorinterface (Install/Uninstall methods) - Add the component to the bootstrap sequence in
pkg/bootstrapper/bootstrapper.go - Consider dependencies and execution order
- Add appropriate tests
We welcome contributions! Here's how to get started:
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Provide a clear description of the changes
- Reference any related issues
- Ensure all CI checks pass
- Update documentation for user-facing changes
- Add tests for new functionality
- Follow the existing code style
- Developer opens PR
- GitHub Actions automatically runs all checks
- All jobs must pass (green) before merge
- Reviews are conducted
- PR is merged to target branch
Recommended branch protection rules for main and dev:
- Require pull request reviews before merging
- Require status checks to pass before merging (Build, Test, Lint, Security, Code Quality)
- Require branches to be up to date before merging
- Require conversation resolution before merging
This project is licensed under the MIT License - see the LICENSE file for details.
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Documentation: See the docs/ directory
- Usage Guide - Installation and configuration
- Design Documentation - Complete system design and technical architecture
- CLAUDE.md - Development guidance for Claude Code