This guide covers setting up a development environment and contributing to Plus Artifacts Server.
- Go 1.21+ - Download
- Git - Version control
- Make - Build automation
- Docker (optional) - For containerized development
- curl - For API testing
- VS Code with Go extension
- Postman or Insomnia for API testing
- golangci-lint for code linting
- hey for load testing
git clone https://github.com/elastic-io/plus.git
cd plus# Download Go modules
go mod download
# Install development tools
make install-tools# Run tests
make test
# Build binary
make build
# Check code quality
make lintplus/
├── cmd/
│ └── plus/ # Main application
├── internal/
│ ├── api/ # HTTP handlers
│ ├── config/ # Configuration
│ ├── metrics/ # Metrics collection
│ ├── middleware/ # HTTP middleware
│ ├── service/ # Business logic
│ └── types/ # Data structures
├── pkg/
│ ├── client/ # Go client library
│ ├── repo/ # Repository implementations
│ └── storage/ # Storage backends
├── assets/ # Embedded static files
├── docs/ # Documentation
├── scripts/ # Build scripts
├── tests/ # Integration tests
├── testdata/ # Test fixtures
└── Makefile # Build automation
Create config.dev.yaml:
server:
listen: ":8080"
dev_mode: true
read_timeout: "30s"
write_timeout: "30s"
storage:
type: "local"
path: "./storage"
logging:
level: "debug"
format: "text"
security:
cors_enabled: true
cors_origins: ["*"]export PLUS_CONFIG=config.dev.yaml
export PLUS_LISTEN=:8080
export PLUS_STORAGE_PATH=./storage
export PLUS_LOG_LEVEL=debug# Using config file
go run ./cmd/plus --config config.dev.yaml
# Using environment variables
PLUS_LISTEN=:8080 go run ./cmd/plus
# With debug logging
go run ./cmd/plus --config config.dev.yaml --debugInstall air for hot reloading:
go install github.com/cosmtrek/air@latest
# Run with hot reload
airCreate .air.toml:
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = ["--config", "config.dev.yaml"]
bin = "./tmp/main"
cmd = "go build -o ./tmp/main ./cmd/plus"
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html", "yaml"]
kill_delay = "0s"
log = "build-errors.log"
send_interrupt = false
stop_on_root = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
time = false
[misc]
clean_on_exit = false- Define handler in
internal/api/:
func (h *API) NewFeature(ctx *fasthttp.RequestCtx) {
// Parse request
// Validate input
// Call service layer
// Return response
}- Add route in
SetupRouter:
patterns["new_feature"] = regexp.MustCompile(`^/api/new-feature$`)- Add service logic in
internal/service/:
func (s *Service) ProcessNewFeature(ctx context.Context, input Input) (*Output, error) {
// Business logic
return output, nil
}- Add tests:
func TestNewFeature(t *testing.T) {
// Test implementation
}- Implement interface in
pkg/storage/:
type NewStorage struct {
// fields
}
func (s *NewStorage) Store(ctx context.Context, path string, reader io.Reader) error {
// Implementation
}
// Implement other interface methods- Register in factory:
func NewStorage(config Config) Storage {
switch config.Type {
case "new":
return NewNewStorage(config)
// other cases
}
}- Implement in
pkg/repo/:
type NewRepo struct {
storage storage.Storage
}
func (r *NewRepo) UploadPackage(ctx context.Context, repoName string, filename string, reader io.Reader) error {
// Implementation
}
// Implement other interface methods- Register repository type:
func init() {
repo.Register(repo.NEW_TYPE, NewNewRepo)
}# Run all tests
make test
# Run specific package
go test ./internal/service -v
# Run with coverage
make test-coverage
# Run with race detection
go test -race ./...# Run integration tests
go test ./tests -v
# Run specific test
go test ./tests -run TestAPIEndToEnd -v# Install hey
go install github.com/rakyll/hey@latest
# Test API endpoint
hey -n 1000 -c 50 http://localhost:8080/health
# Test file upload
hey -n 100 -c 10 -m POST -D test.rpm http://localhost:8080/repo/test/uploadCreate test fixtures in testdata/:
testdata/
├── packages/
│ ├── test.rpm
│ └── test.deb
├── configs/
│ └── test-config.yaml
└── responses/
└── expected.json
# Enable debug logging
go run ./cmd/plus --config config.dev.yaml --debug
# Or set log level
PLUS_LOG_LEVEL=debug go run ./cmd/plus# CPU profiling
go run ./cmd/plus --config config.dev.yaml --cpuprofile=cpu.prof
# Memory profiling
go run ./cmd/plus --config config.dev.yaml --memprofile=mem.prof
# Analyze profiles
go tool pprof cpu.prof
go tool pprof mem.prof# Install delve
go install github.com/go-delve/delve/cmd/dlv@latest
# Debug application
dlv debug ./cmd/plus -- --config config.dev.yaml
# Debug tests
dlv test ./internal/service# Run all linters
make lint
# Run specific linter
golangci-lint run
# Fix auto-fixable issues
golangci-lint run --fix# Format code
make fmt
# Or manually
gofmt -s -w .
goimports -w .# Run go vet
go vet ./...
# Run staticcheck
staticcheck ./...Currently, Plus uses filesystem storage. For future database support:
- Create migration files:
-- migrations/001_initial.up.sql
CREATE TABLE repositories (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
path VARCHAR(255),
created_at TIMESTAMP DEFAULT NOW()
);- Add migration logic:
func RunMigrations(db *sql.DB) error {
// Migration logic
}Static files are embedded using Go's embed package:
//go:embed static/*
var StaticFiles embed.FSIn development, static files are served from disk:
server:
dev_mode: true # Serves from ./static/ directoryIn production, files are served from embedded assets.
If you have a separate frontend build process:
# Build frontend assets
cd frontend
npm run build
# Copy to static directory
cp -r dist/* ../static/# Dockerfile.dev
FROM golang:1.21-alpine
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o plus ./cmd/plus
EXPOSE 8080
CMD ["./plus", "--config", "config.dev.yaml"]# docker-compose.dev.yml
version: '3.8'
services:
plus:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "8080:8080"
volumes:
- .:/app
- ./storage:/app/storage
environment:
- PLUS_CONFIG=config.dev.yaml
command: air # Hot reload- Enable profiling:
import _ "net/http/pprof"
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()- Collect profiles:
# CPU profile
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
# Memory profile
go tool pprof http://localhost:6060/debug/pprof/heap
# Goroutine profile
go tool pprof http://localhost:6060/debug/pprof/goroutinefunc BenchmarkUploadPackage(b *testing.B) {
service := NewTestService()
b.ResetTimer()
for i := 0; i < b.N; i++ {
service.UploadPackage(context.Background(), "test", "test.rpm", bytes.NewReader(testData))
}
}Run benchmarks:
go test -bench=. ./internal/service
go test -bench=BenchmarkUploadPackage -benchmem ./internal/service# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: '1.21'
- name: Run tests
run: make test
- name: Run linting
run: make lint
- name: Build
run: make buildInstall pre-commit hooks:
# Install pre-commit
pip install pre-commit
# Install hooks
pre-commit installCreate .pre-commit-config.yaml:
repos:
- repo: local
hooks:
- id: go-fmt
name: go fmt
entry: gofmt
language: system
args: [-s, -w]
files: \.go$
- id: go-test
name: go test
entry: make test
language: system
pass_filenames: false- Update version:
// internal/version/version.go
const Version = "1.1.0"- Update changelog:
## [1.1.0] - 2024-02-01
### Added
- New feature X
### Fixed
- Bug Y- Create release:
git tag v1.1.0
git push origin v1.1.0# Build for multiple platforms
make build-all
# Build Docker image
make docker-build
# Create release artifacts
make release-
Port already in use:
lsof -i :8080 kill -9 <PID>
-
Module issues:
go mod tidy go clean -modcache go mod download
-
Permission errors:
chmod -R 755 storage/
-
Build errors:
go clean -cache go build -a ./cmd/plus
- Check configuration file
- Verify storage permissions
- Check port availability
- Review logs for errors
- Test with minimal config
- Check Go version compatibility
See CONTRIBUTING.md for detailed contribution guidelines.
- GitHub Issues - Bug reports and feature requests
- GitHub Discussions - Questions and community support
- Code Review - Implementation feedback
Happy coding! 🚀