Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
3236ac5
Merge pull request #233 from vbelouso/fix-pgsql-cron
vbelouso Jan 19, 2026
da5197b
fix: resolve logic bug causing skipped instances in AWS stocker (#240)
vbelouso Jan 20, 2026
d989adb
fix: initialize repository slices to return empty array instead of nu…
vbelouso Jan 20, 2026
e919aaa
chore: remove pagination size limit (#234)
vbelouso Jan 20, 2026
ce7f864
fix: Fixed cluster age calculation, unit tests and DTO transform func…
r2dedios Jan 22, 2026
bb9c6d0
fix: Replaced actions API endpoint and fixed Cron+Scheduled Actions m…
r2dedios Jan 22, 2026
9422c0c
fix: zero value expense for AWS stocker (#248)
r2dedios Jan 22, 2026
bb58405
fix(db): correct schema inconsistencies and logical errors (#246)
r2dedios Jan 26, 2026
337dce5
chore(helm): add Hashicorp Vault integration (#255)
vbelouso Feb 2, 2026
f894c17
docs: add Hashicorp Vault integration guide (#254)
vbelouso Feb 2, 2026
f6bcc08
fix(agent): Fixed CronActions re-scheduling (#256)
r2dedios Feb 3, 2026
715e2ad
fix(scanner): Fixed creation timestamp capture for OSD clusters (#258)
r2dedios Feb 3, 2026
3d39c36
chore: add requester and description parameters (#252)
r2dedios Mar 2, 2026
0764dc4
fix(agent): resolve memory leak in CronActions and unsafe type assert…
r2dedios Mar 11, 2026
02a70d4
feat(dto): Added Description and Requester parameters to DTOs
r2dedios Mar 12, 2026
dda2ab8
Fix: Fixed context managemenet from API handlers to service/repository
r2dedios Mar 12, 2026
d73489f
refactor(api): eliminate code duplication in power handlers and fix t…
r2dedios Mar 12, 2026
dfdba21
refactor(db): remove context.TODO() and add scanner timeout
r2dedios Mar 16, 2026
778a1bc
docs(db_client): add deprecation comments to non-context wrapper methods
r2dedios Mar 16, 2026
f092a1f
fix(dto): propagate conversion errors and add GoDoc comments
r2dedios Mar 17, 2026
ef4915a
fix(aws): continue processing instances on conversion error instead o…
r2dedios Mar 17, 2026
30ff426
feat(api): implement PATCH endpoint for accounts
r2dedios Mar 20, 2026
c81eec2
feat(api): implement PATCH endpoint for clusters
r2dedios Mar 20, 2026
966aa93
test(dto): add error handling tests for ToInventoryList converters
r2dedios Mar 20, 2026
b73b561
docs(claude): condense and streamline CLAUDE.md documentation
r2dedios Mar 20, 2026
ecb6806
fix(agent): Solved conccurency access problem when accessing schedule…
r2dedios Mar 20, 2026
eed9754
fix(db): use named returns to prevent rollback after commit
r2dedios Mar 23, 2026
b575315
fix(scanner): handle cluster addition errors in instance processing
r2dedios Mar 23, 2026
40a8965
tests: Fixed integration tests errors by restoring static values afte…
r2dedios Mar 23, 2026
c517fdd
chore(deps): update Go dependencies
r2dedios Apr 29, 2026
c0f70e6
docs(api): fix Swagger response type for account PATCH endpoint
r2dedios Apr 29, 2026
b97aaa0
docs(db): fix godoc formatting for deprecated methods
r2dedios Apr 29, 2026
3106ffc
refactor(repositories): extract materialized view refresh logic
r2dedios Apr 29, 2026
2211c58
fix(repositories): use correct query for terminated instances refresh
r2dedios Apr 29, 2026
a3a7382
fix(api): return HTTP status codes from inventory Refresh handler
r2dedios Apr 29, 2026
73d454a
fix(api): prevent nil dereferences in action, expense, and scanner ha…
r2dedios Apr 29, 2026
52e3b86
fix(repositories): add ErrNoClustersInAccount and fix wrong error mes…
r2dedios Apr 29, 2026
7df56de
fix(dto): add requester/description fields to action DTOs and fix JSO…
r2dedios Apr 29, 2026
2fe2124
fix(clients): propagate context in ProcessInstantAction
r2dedios Apr 29, 2026
d5f2467
docs(services): fix duplicate comment on GetInstances method
r2dedios Apr 29, 2026
d21541f
fix(repositories): use conditional rollback in action Create transaction
r2dedios Apr 29, 2026
fcbfc36
fix(services): wrap repository errors with service-layer context
r2dedios Apr 29, 2026
85eef3c
fix(aws): guard against nil ResourceTagSet in Route53 zone discovery
r2dedios Apr 29, 2026
078d472
docs(api): fix Swagger annotations for action Update and inventory Re…
r2dedios Apr 29, 2026
b0a1e6c
docs: fix broken link, duplicate env var, and update CLAUDE.md
r2dedios Apr 29, 2026
5c41fc8
fix(db): cascade delete events on cluster/instance removal
r2dedios Apr 29, 2026
7b22fa4
fix(events): handle nullable resource_id from event views
r2dedios Apr 29, 2026
10e36ea
docs(claude): clarify Makefile usage for compilation checks
r2dedios Apr 29, 2026
4566711
fix(ci): pin golangci-lint version to match local environment
r2dedios Apr 29, 2026
f611b2a
fix(db): replace NamedQuery with PrepareNamed to ensure resource cleanup
r2dedios Apr 29, 2026
781e53f
test(integration): fix assertions after service error wrapping and te…
r2dedios Apr 29, 2026
8686a71
fix: address multiple runtime bugs found during release-0.5.1 audit
r2dedios May 26, 2026
d1f3aa7
docs(db): rewrite v0.5-to-v0.5.1 migration guide
r2dedios May 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/validate-pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ jobs:
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v8
with:
version: v2.6.2
only-new-issues: true
args: --whole-files
version: v2.1

call-unit-tests:
name: Go Unit tests
Expand Down
188 changes: 188 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# CLAUDE.md

This file provides guidance to Claude Code when working with this repository.

## Project Overview

**ClusterIQ** is an inventory and cost estimation platform for OpenShift clusters across multi-cloud environments (currently AWS only). It provides automated discovery, cost tracking, and lifecycle management.

**Architecture Components:**
1. **Scanner**: CronJob that discovers cloud resources using "Stocker" pattern
2. **API Server**: REST API (Gin framework) for inventory queries and cluster operations
3. **Agent**: gRPC service handling cluster power operations (instant, scheduled, recurring)

**Repository Structure:**
```
cmd/ # Application entry points (api, scanner, agent)
internal/
├── actions/ # Action system (instant, scheduled, cron)
├── cloud_providers/ # AWS, GCP, Azure abstractions
├── cloud_executors/ # Action execution
├── stocker/ # Resource discovery
├── repositories/ # Data access layer
├── services/ # Business logic
├── api/handlers/ # HTTP handlers
└── models/ # DTO, DB, domain models
db/sql/ # Schema definitions (init.sql, cron.sql)
test/integration/ # Integration tests
```

## Essential Commands

**CRITICAL**: Always use Makefile. Never use direct `go build`, `go test`, or `go run`.
This includes compilation checks after code changes — use `make local-build` instead of `go build ./...`.

```bash
# Development
make check-dependencies # Check before building
make all # Stop, build, start dev environment
make start-dev # Start services
make stop-dev # Stop services

# Building
make build # All container images
make local-build # All local binaries

# Testing
make go-unit-tests
make go-integration-tests
make go-tests # All tests
make lint # Lint entire project
make lint-staged # Lint staged files only

# Code Generation
make generate-converters # Goverter (DB to DTO)
make swagger-doc # OpenAPI docs
```

## Architecture Patterns

**Layered Architecture:**
- Handlers → Services → Repositories → Database
- Each layer has interfaces for testability
- Repository returns `repositories.ErrNotFound` for missing resources
- Repository returns `repositories.ErrNoClustersInAccount` when an account exists but has no clusters
- Services wrap all errors with `fmt.Errorf` context before returning to handlers
- Handlers map errors to HTTP status codes (404, 400, 500)

**Key Patterns:**
- **Repository Pattern**: Data access abstraction
- **Service Layer**: Business logic with dependency injection
- **Stocker Pattern**: Cloud resource discovery via `MakeStock()`
- **Action Channel**: Unbuffered channel for decoupled action dispatch
- **Event Tracker**: Audit logging with `EventService.StartTracking()`
- **Functional Options**: AWS connections use `WithEC2()`, `WithRoute53()`, etc.

## Testing Guidelines

**Service Layer Mocking:**
```go
// Mock repositories with function pointers
type mockClusterRepository struct {
getClusterByIDFn func(ctx context.Context, clusterID string) (*db.ClusterDBResponse, error)
}

func (m *mockClusterRepository) GetClusterByID(ctx context.Context, clusterID string) (*db.ClusterDBResponse, error) {
if m.getClusterByIDFn != nil {
return m.getClusterByIDFn(ctx, clusterID)
}
return nil, nil
}
```

**gRPC Mocking:**
- Mock the underlying `pb.AgentServiceClient` interface, not the wrapper
- Return actual Go errors: `return nil, errTest`
- DON'T use response error codes for Go errors: `&pb.Response{Error: 1}` returns `nil` error

**Test Organization:**
- Group by method: `TestServiceName_MethodName(t *testing.T)`
- Use sub-tests: `t.Run("success", func(t *testing.T) { ... })`
- Naming: `testServiceName_MethodName_Scenario`
- All interface methods must be implemented in mocks (even if unused)

**Coverage Analysis:**
```bash
go test -coverprofile=coverage.out ./internal/services/...
go tool cover -func=coverage.out | grep -v "100.0%" # Find uncovered
go tool cover -html=coverage.out -o coverage.html # Visual
```

## Development Workflow

1. Make code changes
2. Run `make lint-staged` before committing
3. Run relevant tests: `make go-unit-tests`
4. For API changes: update Swagger with `make swagger-doc`
5. For DB changes: update `db/sql/init.sql` or add data migration in `doc/releases/`
6. For protobuf changes: `make local-build-agent`
7. For goverter changes: `make generate-converters`

**Commit Convention:**
- Use conventional commits format: `type(scope): brief description`
- Keep messages to single line
- Types: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`
- Examples:
- `feat(api): implement PATCH endpoint for accounts`
- `fix(aws): continue processing instances on conversion error`
- `test(dto): add error handling tests for converters`

## Critical Implementation Notes

**Cloud Support:**
- AWS: Fully implemented ✅
- GCP/Azure: Interface stubs only ❌

**Data Consistency:**
- Each repository method = separate transaction
- No distributed transactions
- Eventual consistency for action status updates

**PATCH Endpoints:**
- Use pointer fields in DTOs to distinguish null from empty
- Dynamic SQL query building with positional parameters
- Verify resource exists before updating
- Refresh materialized views after successful update
- Return updated resource with HTTP 200

## Configuration

Environment variables:
- `CIQ_AGENT_URL` - Agent gRPC endpoint (default: "agent:50051")
- `CIQ_API_URL` - API endpoint used by Scanner and Agent (not by the API server itself)
- `CIQ_DB_URL` - PostgreSQL connection (default: "postgresql://pgsql:5432/clusteriq")
- `CIQ_CREDS_FILE` - Cloud provider credentials file
- `CIQ_LOG_LEVEL` - Log verbosity (default: "INFO")

## AWS Integration Details

**Cluster Discovery:**
- OpenShift tags: `kubernetes.io/cluster/<cluster-name>-<infraID>`
- Console URLs via Route53 DNS: `console-openshift-console.apps.*`

**Instance Timestamps:**
- Primary: Root EBS volume `AttachTime` (via `instance.RootDeviceName`)
- Fallback: Instance `LaunchTime`
- NEVER hardcode device names (varies by instance type)

**Cost Explorer:**
- 14-day rolling window, DAILY granularity
- Metric: UnblendedCost
- One Expense object per instance per day

**EC2 Operations:**
- Start: Only targets `stopped` instances
- Stop: Only targets `running` instances
- Prevents idempotency errors

## Code Generation

**Goverter**: Type-safe DB ↔ DTO converters
- Definitions: `internal/models/convert/`
- Generated: `internal/models/convert/generated.go`

**Swagger**: OpenAPI from Go comments
- Annotations in handlers and `cmd/api/server.go`

**Protobuf**: gRPC code from `cmd/agent/proto/agent.proto`
- Auto-generated during `make local-build-agent`
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ available for every cloud provider:
The following graph shows the architecture of this project:
![ClusterIQ architecture diagram](./doc/architecture.png)

### Secrets Management (Optional)

By default, Cluster IQ uses standard Kubernetes Secrets. For production environments, you can integrate with HashiCorp Vault.
See [Vault Integration Guide](doc/vault/README.md) for detailed setup instructions.

## Installation
ClusterIQ was designed to run in K8s/Openshift platforms, but it can also run in
local using `podman-compose`.
Expand Down Expand Up @@ -152,7 +157,7 @@ For deploying ClusterIQ in local for development purposes, check the following

## DB Backup
For backing up or restoring the ClusterIQ database, check the following
[document](./doc/db-backup.md)
[document](./doc/developers/db-backup.md)
This document also describes how to manage data migration when a new release of
ClusterIQ changes DB data structure.

Expand All @@ -165,8 +170,7 @@ Available configuration via Env Vars:
| CIQ_AGENT_POLLING_SECONDS_INTERVAL | integer (Default: 30) | ClusterIQ Agent polling time (seconds) |
| CIQ_AGENT_URL | string (Default: "agent:50051") | ClusterIQ Agent listen URL |
| CIQ_API_LISTEN_URL | string (Default: "0.0.0.0:8080") | ClusterIQ API listen URL |
| CIQ_API_URL | string (Default: "") | ClusterIQ API public endpoint |
| CIQ_AGENT_LISTEN_URL | string (Default: "0.0.0.0:50051") | ClusterIQ Agent listen URL |
| CIQ_API_URL | string (Default: "") | ClusterIQ API endpoint (used by Scanner) |
| CIQ_DB_URL | string (Default: "postgresql://pgsql:5432/clusteriq") | ClusterIQ DB URL |
| CIQ_CREDS_FILE | string (Default: "") | Cloud providers accounts credentials file |
| CIQ_LOG_LEVEL | string (Default: "INFO") | ClusterIQ Logs verbosity mode |
Expand Down
18 changes: 13 additions & 5 deletions cmd/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,13 @@ func NewAgent(cfg *config.AgentConfig, logger *zap.Logger) (*Agent, error) {

// StartAgentServices starts every AgentService on a separate thread(go-routine)
func (a *Agent) StartAgentServices() error {
var err error
errChan := make(chan error, AgentServicesCount)

// Starting InstantAgentService
a.wg.Add(1)
go func() {
defer a.wg.Done()
if err = a.ias.Start(); err != nil {
if err := a.ias.Start(); err != nil {
errChan <- fmt.Errorf("instant AgentService (gRPC) failed: %w", err)
return
}
Expand All @@ -124,7 +123,7 @@ func (a *Agent) StartAgentServices() error {
a.wg.Add(1)
go func() {
defer a.wg.Done()
if err = a.sas.Start(); err != nil {
if err := a.sas.Start(); err != nil {
errChan <- fmt.Errorf("scheduled Agent Service failed: %w", err)
return
}
Expand All @@ -135,7 +134,7 @@ func (a *Agent) StartAgentServices() error {
a.wg.Add(1)
go func() {
defer a.wg.Done()
if err = a.eas.Start(); err != nil {
if err := a.eas.Start(); err != nil {
errChan <- fmt.Errorf("executor Agent Service failed: %w", err)
return
}
Expand Down Expand Up @@ -221,7 +220,16 @@ func (a Agent) signalHandler(signal os.Signal) error {
a.logger.Warn("Shutting down server...", zap.String("signal", signal.String()))
}

for _, item := range a.sas.schedule {
// Copy schedule map under lock to avoid race condition during iteration
a.sas.mutex.Lock()
scheduleCopy := make(map[string]scheduleItem, len(a.sas.schedule))
for k, v := range a.sas.schedule {
scheduleCopy[k] = v
}
a.sas.mutex.Unlock()

// Iterate over the copy (without holding lock) to cancel all scheduled actions
for _, item := range scheduleCopy {
cancel := item.cancel
action := item.action
a.logger.Warn("Cancelling ScheduledAction", zap.String("action_id", action.GetID()))
Expand Down
Loading