Add comprehensive test suite and documentation
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
- Complete unit, integration, and E2E test coverage (189 test cases) - Enhanced CI/CD pipeline with race detection and quality checks - Comprehensive godoc documentation for all packages - Updated README with API docs, examples, and deployment guides
This commit is contained in:
345
README.md
345
README.md
@@ -1,15 +1,338 @@
|
||||
# geoipapi
|
||||
# GeoIP API
|
||||
|
||||
This provides a small daemon intended to run within for example
|
||||
a kubernetes to provide MaxMind GeoIP data to other services over
|
||||
HTTP.
|
||||
A high-performance HTTP service that provides MaxMind GeoIP data for IP geolocation lookups. Designed to run as a lightweight daemon within Kubernetes clusters to serve geolocation data to other services.
|
||||
|
||||
The available APIs are `/api/country?ip=192.0.2.1` returning the
|
||||
country of the IP and `/api/json?ip=192.0.2.1` providing the maxmind
|
||||
data in JSON format.
|
||||
## Features
|
||||
|
||||
OpenTelemetry tracing is supported with the standard Traceparent http
|
||||
header, and configuration through the standard environment variables.
|
||||
(Work great with the opentelemetry collector operator).
|
||||
- **Fast HTTP API** for IP geolocation lookups
|
||||
- **Multiple response formats**: country codes and full JSON data
|
||||
- **OpenTelemetry tracing** with standard Traceparent headers
|
||||
- **Automatic database discovery** in standard system paths
|
||||
- **Health check endpoint** with actual database verification
|
||||
- **Go client library** for easy integration
|
||||
- **Comprehensive test coverage** with unit, integration, and E2E tests
|
||||
|
||||
There's a small Go API client in `client/geoipapi`.
|
||||
## API Endpoints
|
||||
|
||||
### Country Lookup
|
||||
```
|
||||
GET /api/country?ip=192.0.2.1
|
||||
```
|
||||
Returns the lowercase ISO country code (e.g., `us`, `gb`)
|
||||
|
||||
### Full JSON Data
|
||||
```
|
||||
GET /api/json?ip=192.0.2.1
|
||||
```
|
||||
Returns complete MaxMind GeoIP data in JSON format including:
|
||||
- Country, city, and region information
|
||||
- Latitude/longitude coordinates
|
||||
- ISP and organization data (if available)
|
||||
- Time zone information
|
||||
|
||||
### Health Check
|
||||
```
|
||||
GET /healthz
|
||||
```
|
||||
Performs an actual GeoIP lookup to verify database connectivity and returns the country code for a test IP.
|
||||
|
||||
## Installation
|
||||
|
||||
### From Source
|
||||
```bash
|
||||
go build -o geoipapi
|
||||
./geoipapi
|
||||
```
|
||||
|
||||
### Using Docker
|
||||
```bash
|
||||
docker build -t geoipapi .
|
||||
docker run -p 8009:8009 geoipapi
|
||||
```
|
||||
|
||||
## Database Setup
|
||||
|
||||
The service automatically searches for MaxMind databases in standard locations:
|
||||
|
||||
- `/usr/share/GeoIP/` (Linux default)
|
||||
- `/usr/local/share/GeoIP/` (FreeBSD)
|
||||
- `/opt/local/share/GeoIP/` (MacPorts)
|
||||
- `/opt/homebrew/var/GeoIP/` (Homebrew)
|
||||
|
||||
### Supported Database Files
|
||||
|
||||
- **Country databases**: `GeoIP2-Country.mmdb`, `GeoLite2-Country.mmdb`
|
||||
- **City databases**: `GeoIP2-City.mmdb`, `GeoLite2-City.mmdb`
|
||||
- **ISP databases**: `GeoIP2-ISP.mmdb`
|
||||
|
||||
### Installing GeoLite2 Databases (Free)
|
||||
|
||||
1. Create a free MaxMind account at https://www.maxmind.com/en/geolite2/signup
|
||||
2. Download the databases manually, or
|
||||
3. Use the built-in MaxMind package for automatic downloads:
|
||||
|
||||
```go
|
||||
import "go.ntppool.org/geoipapi/maxmind"
|
||||
|
||||
maxmind.LicenseKey = "your_license_key_here"
|
||||
maxmind.EditionIDs = "GeoLite2-City,GeoLite2-Country"
|
||||
maxmind.Path = "/usr/share/GeoIP/"
|
||||
|
||||
err := maxmind.DownloadGeoLite2DB()
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- **OpenTelemetry**: Standard OTel environment variables are supported
|
||||
- `OTEL_EXPORTER_OTLP_ENDPOINT`
|
||||
- `OTEL_SERVICE_NAME`
|
||||
- `OTEL_RESOURCE_ATTRIBUTES`
|
||||
|
||||
### Server Configuration
|
||||
|
||||
The server runs on port 8009 by default with the following timeouts:
|
||||
- **Read timeout**: 1 second
|
||||
- **Write timeout**: 10 seconds
|
||||
|
||||
## OpenTelemetry Support
|
||||
|
||||
The service includes comprehensive OpenTelemetry instrumentation:
|
||||
|
||||
- **HTTP requests** are automatically traced
|
||||
- **Database lookups** are instrumented with spans
|
||||
- **Health checks** are filtered from tracing to reduce noise
|
||||
- **Custom attributes** include IP addresses and operation details
|
||||
|
||||
Tracing works seamlessly with the OpenTelemetry Collector and common observability platforms.
|
||||
|
||||
## Go Client Library
|
||||
|
||||
Use the provided Go client for easy integration:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/netip"
|
||||
"os"
|
||||
|
||||
"go.ntppool.org/geoipapi/client/geoipapi"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Set the service endpoint
|
||||
os.Setenv("geoip_service", "geoip-service:8009")
|
||||
|
||||
ctx := context.Background()
|
||||
ip := netip.MustParseAddr("8.8.8.8")
|
||||
|
||||
city, err := geoipapi.GetCity(ctx, ip)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("IP: %s\n", ip)
|
||||
fmt.Printf("Country: %s\n", city.Country.IsoCode)
|
||||
fmt.Printf("City: %s\n", city.City.Names["en"])
|
||||
fmt.Printf("Location: %f, %f\n", city.Location.Latitude, city.Location.Longitude)
|
||||
}
|
||||
```
|
||||
|
||||
### Client Configuration
|
||||
|
||||
Set the `geoip_service` environment variable to point to your GeoIP API service:
|
||||
|
||||
```bash
|
||||
export geoip_service="geoip-service.default.svc.cluster.local:8009"
|
||||
```
|
||||
|
||||
## Command Line Usage
|
||||
|
||||
The service can also be used as a command-line tool for IP lookups:
|
||||
|
||||
```bash
|
||||
./geoipapi 8.8.8.8 1.1.1.1 192.168.1.1
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
8.8.8.8: us
|
||||
1.1.1.1: us
|
||||
192.168.1.1:
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Running Tests
|
||||
|
||||
The project includes comprehensive test coverage:
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
go test ./...
|
||||
|
||||
# Run tests with race detection
|
||||
go test -race ./...
|
||||
|
||||
# Run only fast tests
|
||||
go test -short ./...
|
||||
|
||||
# Run with coverage
|
||||
go test -cover ./...
|
||||
```
|
||||
|
||||
### Test Types
|
||||
|
||||
- **Unit tests**: Test individual functions and components
|
||||
- **Integration tests**: Test HTTP API endpoints with a running server
|
||||
- **End-to-end tests**: Test complete client-server workflows
|
||||
- **Race condition tests**: Verify thread safety under concurrent load
|
||||
|
||||
### Code Quality
|
||||
|
||||
```bash
|
||||
# Format code
|
||||
gofumpt -w .
|
||||
|
||||
# Lint code
|
||||
go vet ./...
|
||||
|
||||
# Verify dependencies
|
||||
go mod verify
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Core Components
|
||||
|
||||
1. **HTTP Server** (`geoipapi.go`): Main API server with endpoint handlers
|
||||
2. **MaxMind Package** (`maxmind/`): Database download and management utilities
|
||||
3. **Client Library** (`client/geoipapi/`): Go client for consuming the HTTP API
|
||||
|
||||
### Database Discovery
|
||||
|
||||
The service automatically discovers MaxMind databases by:
|
||||
|
||||
1. Searching standard system paths
|
||||
2. Looking for supported database filenames
|
||||
3. Opening the first available database for each type
|
||||
4. Gracefully handling missing databases
|
||||
|
||||
### Error Handling
|
||||
|
||||
- **Invalid IP addresses** return HTTP 500 with "data error"
|
||||
- **Missing databases** are detected during health checks
|
||||
- **Network errors** in the client include proper context
|
||||
- **Tracing errors** are recorded in spans for debugging
|
||||
|
||||
## Deployment
|
||||
|
||||
### Kubernetes
|
||||
|
||||
Example Kubernetes deployment:
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: geoipapi
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: geoipapi
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: geoipapi
|
||||
spec:
|
||||
containers:
|
||||
- name: geoipapi
|
||||
image: your-registry/geoipapi:latest
|
||||
ports:
|
||||
- containerPort: 8009
|
||||
env:
|
||||
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
value: "http://otel-collector:4317"
|
||||
- name: OTEL_SERVICE_NAME
|
||||
value: "geoipapi"
|
||||
volumeMounts:
|
||||
- name: geoip-data
|
||||
mountPath: /usr/share/GeoIP
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 8009
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 8009
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 20
|
||||
volumes:
|
||||
- name: geoip-data
|
||||
configMap:
|
||||
name: geoip-databases
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: geoipapi
|
||||
spec:
|
||||
selector:
|
||||
app: geoipapi
|
||||
ports:
|
||||
- port: 8009
|
||||
targetPort: 8009
|
||||
```
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
- **Database caching**: MaxMind databases are loaded once on startup
|
||||
- **Connection pooling**: HTTP client uses connection pooling for better performance
|
||||
- **Concurrent requests**: Server handles multiple concurrent requests efficiently
|
||||
- **Memory usage**: Minimal memory footprint suitable for container environments
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes with tests
|
||||
4. Run the full test suite: `go test ./...`
|
||||
5. Format code: `gofumpt -w .`
|
||||
6. Submit a pull request
|
||||
|
||||
### CI/CD
|
||||
|
||||
The project uses Drone CI with the following pipeline:
|
||||
|
||||
1. **Dependencies**: Download Go modules
|
||||
2. **Testing**: Run unit, integration, and race tests
|
||||
3. **Code Quality**: Run `go vet`, `gofumpt`, and `go mod verify`
|
||||
4. **Build**: Compile the binary
|
||||
5. **Docker**: Build and push container image
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the terms specified in the LICENSE file.
|
||||
|
||||
## Support
|
||||
|
||||
For issues and questions:
|
||||
|
||||
- **Bug reports**: Create an issue in the GitHub repository
|
||||
- **Feature requests**: Submit a feature request with use case details
|
||||
- **Documentation**: Check the Go docs: `go doc go.ntppool.org/geoipapi`
|
||||
|
||||
## Related Projects
|
||||
|
||||
- **MaxMind GeoIP2**: https://www.maxmind.com/en/geoip2-services-and-databases
|
||||
- **OpenTelemetry Go**: https://github.com/open-telemetry/opentelemetry-go
|
||||
- **Kubernetes ingress-nginx**: https://github.com/kubernetes/ingress-nginx (inspiration for MaxMind handling)
|
Reference in New Issue
Block a user