Skip to content

feat: implement instance types and resource limits#42

Merged
poyrazK merged 15 commits intomainfrom
feature/instance-types
Jan 30, 2026
Merged

feat: implement instance types and resource limits#42
poyrazK merged 15 commits intomainfrom
feature/instance-types

Conversation

@poyrazK
Copy link
Copy Markdown
Owner

@poyrazK poyrazK commented Jan 30, 2026

This PR introduces support for instance types and resource limits across the platform.

Key Changes:

  • Core: Added InstanceType domain model and repository.
  • Service: InstanceService now resolves instance types to set CPU, Memory, and Disk limits during provisioning.
  • Database: Added instance_types table and seeded default types (basic, standard, performance).
  • SDK/CLI: Added instanceType support to the Go SDK and a --type flag to the CLI.
  • API: Updated the launch endpoint to accept instance_type.
  • Docs: Fully updated API/CLI references, architecture, and database documentation.

All tests passed successfully.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added instance type selection for instance launches. Users can now specify instance types (basic-1, basic-2, standard-1, etc.) when creating instances.
    • Added --type (-t) CLI flag to the instance launch command (default: basic-2).
    • Added GET /instance-types API endpoint to list available instance types with resource specifications and pricing.
  • Documentation

    • Updated CLI and API reference documentation to reflect instance type configuration options.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 30, 2026

📝 Walkthrough

Walkthrough

This PR adds instance type support to the cloud platform, introducing a new configurable parameter for selecting instance specifications (CPU, memory, disk) when launching instances. A new InstanceType domain model, database schema, repositories, and service layer enable instance type management. The LaunchInstance API is updated to accept an instanceType parameter, which is resolved to fetch resource specifications during provisioning. CLI, HTTP handlers, and comprehensive documentation are updated to expose this feature.

Changes

Cohort / File(s) Summary
Domain Models
internal/core/domain/instance.go, internal/core/domain/instance_type.go, internal/core/domain/autoscaling.go, internal/core/domain/container.go
Added InstanceType field to Instance, ScalingGroup, and Deployment structs; introduced new InstanceType struct with CPU, memory, disk, pricing, and category details.
Port Interfaces
internal/core/ports/instance.go, internal/core/ports/instance_type.go, internal/core/ports/compute_options.go
Updated LaunchInstance method signature to include instanceType parameter; added new InstanceTypeRepository and InstanceTypeService interfaces; extended CreateInstanceOptions with CPU/memory/disk limit fields.
Service Implementation
internal/core/services/instance.go, internal/core/services/instance_type.go
Added InstanceTypeRepository dependency to InstanceService; updated LaunchInstance to validate and use instance type; enhanced Provision to resolve instance type specs and pass resource limits to backend; new InstanceTypeService for listing types.
Service & Worker Call Sites
internal/core/services/autoscaling_worker.go, internal/core/services/container_worker.go, internal/core/services/stack.go, internal/core/services/...
Updated all LaunchInstance call sites to include the new instanceType parameter in the correct position.
PostgreSQL Repository
internal/repositories/postgres/instance_repo.go, internal/repositories/postgres/instance_type_repo.go, internal/repositories/postgres/migrations/075_*
Added instance_type column to instances table (default 'basic-2'); created new instance_types table with instance specifications and seeded predefined types; updated repository methods to handle InstanceType field; new CRUD repository for instance types.
No-Op Repository
internal/repositories/noop/adapters.go
Implemented NoopInstanceTypeRepository with hardcoded instance type definitions (basic-1, basic-2) for testing.
HTTP Handler & Routing
internal/handlers/instance_handler.go, internal/handlers/instance_type_handler.go, internal/api/setup/router.go, internal/api/setup/dependencies.go
Added InstanceType field to LaunchRequest payload; new InstanceTypeHandler with List endpoint; wired instance type service into dependency injection; registered /instance-types GET route with auth middleware.
SDK Client
pkg/sdk/compute.go
Updated LaunchInstance method signature to include instanceType parameter; added InstanceType field to SDK Instance struct.
CLI
cmd/cloud/instance.go
Added --type / -t flag to instance launch command with default value "basic-2".
Test Files
internal/core/services/*_test.go, internal/handlers/*_test.go, internal/repositories/*_test.go, internal/workers/*_test.go, pkg/sdk/compute_test.go, internal/repositories/k8s/*_test.go
Updated test mocks and call sites to match new LaunchInstance signature; added MockInstanceTypeRepo; configured instance type repository in test setups; updated assertions and mock expectations.
Documentation
docs/architecture.md, docs/api-reference.md, docs/cli-reference.md, docs/database.md, docs/implementation.md, docs/FEATURES.md, docs/vision.md, docs/swagger/docs.go, docs/swagger/swagger.json, docs/swagger/swagger.yaml
Documented new InstanceType domain model; updated API reference with /instance-types endpoint and instance_type field in request/response payloads; added CLI flag documentation; updated Swagger/OpenAPI schemas; updated feature list and vision roadmap.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Handler as HTTP Handler
    participant Service as InstanceService
    participant TypeRepo as InstanceTypeRepository
    participant DB as Database
    participant Backend as Compute Backend

    Client->>Handler: POST /instances {name, image, ports, instance_type, vpc_id, ...}
    Handler->>Service: LaunchInstance(ctx, name, image, ports, instanceType, vpcID, subnetID, volumes)
    Service->>Service: Validate instanceType (default: "basic-2")
    Service->>TypeRepo: GetByID(ctx, instanceType)
    TypeRepo->>DB: SELECT * FROM instance_types WHERE id = ?
    DB-->>TypeRepo: {vcpus: 2, memory_mb: 4096, disk_gb: 50, ...}
    TypeRepo-->>Service: domain.InstanceType
    Service->>Service: Create Instance with InstanceType field
    Service->>Backend: CreateInstance(ctx, CreateInstanceOptions{CPULimit, MemoryLimit, DiskLimit, ...})
    Backend-->>Service: instance created
    Service-->>Handler: domain.Instance {id, name, instance_type, ...}
    Handler-->>Client: 200 OK {data: {id, name, instance_type, ...}}
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 Instance types hop in place,
From basic to performance's grace,
Database seeds a garden of specs,
The provisioner now checks and checks,
Resource limits held so tight—
Cloud compute configured just right! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: implement instance types and resource limits' clearly and concisely describes the main objective of the PR, accurately reflecting the core feature addition across the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/instance-types

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
docs/database.md (1)

120-155: ⚠️ Potential issue | 🟡 Minor

Update the seeded instance types list to be complete.

The documentation is incomplete. The migration seeds seven instance types, but the doc only lists four examples: basic-1, basic-2, standard-1, and performance-2. Add the missing types—standard-2, standard-4, and performance-1—to the documentation to prevent drift.

The default value basic-2 is correct and matches the migration.

internal/core/services/instance_test.go (1)

527-536: ⚠️ Potential issue | 🟡 Minor

Complete or remove the placeholder volume lookup test.

This test currently doesn’t exercise any behavior or make assertions, so it provides no coverage. Consider wiring it to LaunchInstance (or remove it) and assert the expected repository calls.

If you want, I can draft a focused test case that exercises volume lookup by ID and name.

🤖 Fix all issues with AI agents
In `@docs/api-reference.md`:
- Around line 176-189: The documented response for GET /instance-types is
missing fields present on the InstanceType model; update the example JSON and
schema to include network_mbps, price_per_hour, and category so it matches the
serialized API output. Locate the GET /instance-types section and the
InstanceType examples and add the network_mbps (number), price_per_hour
(number/string as used in serialization), and category (string) properties to
each instance object and any schema tables or descriptions referencing
InstanceType.

In `@internal/core/ports/instance_type.go`:
- Around line 9-15: The repository port InstanceTypeRepository is missing
Create, Update and Delete methods; add these CRUD methods keeping
context.Context as the first parameter and using domain.InstanceType types:
declare Create(ctx context.Context, it *domain.InstanceType)
(*domain.InstanceType, error), Update(ctx context.Context, it
*domain.InstanceType) (*domain.InstanceType, error) and Delete(ctx
context.Context, id string) error so the interface contains List, GetByID,
Create, Update and Delete.

In `@internal/core/services/instance_test.go`:
- Around line 55-66: The test uses a non-strict mock expectation for instance
type lookup (itRepo.On("GetByID", ...).Maybe()) so the new instance type
resolution can be skipped; remove .Maybe() on the GetByID expectation in
TestLaunchInstanceSuccess (and any similar LaunchInstance tests) so the
expectation is strict, and add an assertion for the mock (e.g., defer
itRepo.AssertExpectations(t)) alongside the existing repo.AssertExpectations(t)
to ensure GetByID was actually called.

In `@internal/core/services/instance.go`:
- Around line 169-175: The code silently leaves cpuLimit/memLimit/diskLimit at
zero when s.instanceTypeRepo.GetByID(ctx, inst.InstanceType) returns an error;
change this to handle it explicitly by checking if itErr != nil and
returning/propagating a clear error from the surrounding provisioning method (or
at minimum log a warning and apply safe fallback limits from config) instead of
continuing with zero values — update the GetByID error handling so cpuLimit,
memLimit, and diskLimit are only set when itErr == nil and otherwise
short-circuit with an error (or set documented fallback constants) to avoid
unintended unlimited/default backend limits.

In `@internal/handlers/instance_handler.go`:
- Around line 41-47: The InstanceType field in the request struct (InstanceType
string) is optional but its JSON tag lacks omitempty; update the struct tag for
InstanceType (the field named InstanceType in the request struct in
internal/handlers/instance_handler.go) to include `omitempty` in the json tag
(e.g., `json:"instance_type,omitempty"`) so optional fields follow the project's
convention.

In `@internal/repositories/postgres/instance_type_repo.go`:
- Around line 35-43: The iteration over rows in the function that builds the
types slice (using rows.Next() and r.scanInstanceType) is missing a
post-iteration check for rows.Err(); after the for loop, call rows.Err() and if
non-nil return nil and that error (or wrap it) so any iteration errors (e.g.,
connection issues) are propagated instead of silently ignored.

In `@pkg/sdk/compute.go`:
- Around line 50-59: The Instance struct is missing the InstanceType field, so
although LaunchInstance sends "instance_type" consumers won't see it in
responses; add a string field named InstanceType to the Instance struct with the
JSON tag `json:"instance_type,omitempty"` (preserve existing field
ordering/formatting with other fields) and ensure any related
marshaling/unmarshaling expectations remain unchanged so listing/getting
instances surface the instance type.
🧹 Nitpick comments (13)
internal/core/domain/autoscaling.go (1)

35-43: Add omitempty if instance_type is optional for scaling groups.

If existing scaling groups can omit this field, serialize it as optional to avoid emitting empty strings and to stay consistent with other domain structs.

✅ Suggested change
-	InstanceType   string             `json:"instance_type"`              // NEW: Configuration type
+	InstanceType   string             `json:"instance_type,omitempty"`     // NEW: Configuration type

As per coding guidelines, include json tags with omitempty for optional fields.

internal/core/domain/container.go (1)

24-32: Add omitempty if instance_type is optional for deployments.

If the field is not mandatory, omit it in JSON to avoid emitting empty strings and align with the guideline for optional fields.

✅ Suggested change
-	InstanceType string           `json:"instance_type"` // NEW: Configuration type
+	InstanceType string           `json:"instance_type,omitempty"` // NEW: Configuration type

As per coding guidelines, include json tags with omitempty for optional fields.

internal/core/ports/compute_options.go (1)

13-15: Add JSON tags for new limit fields (if these options are serialized).

This keeps zero values out of payloads and aligns with the struct-tag convention for optional fields.

🔧 Proposed update
-	CPULimit    int64    // CPU cores (or millicores)
-	MemoryLimit int64    // Memory in bytes
-	DiskLimit   int64    // Disk in bytes
+	CPULimit    int64    `json:"cpu_limit,omitempty"`    // CPU cores (or millicores)
+	MemoryLimit int64    `json:"memory_limit,omitempty"` // Memory in bytes
+	DiskLimit   int64    `json:"disk_limit,omitempty"`   // Disk in bytes
As per coding guidelines Include `json` tags on struct fields with `omitempty` for optional fields.
docs/implementation.md (1)

62-64: Clarify name-based lookup in the API section.

The interface now says idOrName, while the later API examples still show /instances/{id}. Consider adding a short note that names are accepted to avoid confusion.

docs/swagger/swagger.json (1)

4948-4950: Consider documenting valid instance_type values.

A short description or enum (e.g., basic/standard/performance variants) will improve API discoverability for clients.

Also applies to: 5494-5497, 6349-6351

internal/core/services/stack.go (1)

276-297: Stack-created instances lack instance type and use hardcoded port.

Two concerns with the createInstance call:

  1. Empty instanceType: Passing "" means stack-created instances won't have resource limits (CPU, Memory, Disk) applied. Consider reading an InstanceType property from the template or defaulting to a sensible value like "basic-2".

  2. Hardcoded port "80": The port should be read from props["Ports"] similar to how Name, Image, and VpcID are extracted.

Suggested improvement
 func (s *stackService) createInstance(ctx context.Context, stackID uuid.UUID, logicalID string, props map[string]interface{}) (uuid.UUID, error) {
 	name, _ := props["Name"].(string)
 	image, _ := props["Image"].(string)
+	ports, _ := props["Ports"].(string)
+	instanceType, _ := props["InstanceType"].(string)
 	vpcID, _ := props["VpcID"].(uuid.UUID)

 	if name == "" {
 		name = fmt.Sprintf("%s-%s", logicalID, stackID.String()[:8])
 	}
+	if ports == "" {
+		ports = "80"
+	}
+	if instanceType == "" {
+		instanceType = "basic-2"
+	}

 	var vpcIDPtr *uuid.UUID
 	if vpcID != uuid.Nil {
 		vpcIDPtr = &vpcID
 	}

-	inst, err := s.instanceSvc.LaunchInstance(ctx, name, image, "80", "", vpcIDPtr, nil, nil)
+	inst, err := s.instanceSvc.LaunchInstance(ctx, name, image, ports, instanceType, vpcIDPtr, nil, nil)
pkg/sdk/compute_test.go (1)

73-101: Consider verifying instance_type in the request body.

The test correctly passes "basic-2" as the new instanceType argument, but the request body assertions (lines 86-87) only check name and image. For completeness, consider also asserting that instance_type is included in the payload.

Optional enhancement
 		assert.Equal(t, computeNewInstance, body["name"])
 		assert.Equal(t, "nginx", body["image"])
+		assert.Equal(t, "basic-2", body["instance_type"])
docs/swagger/swagger.yaml (1)

727-729: Consider removing the "NEW:" prefix from the description.

The description "NEW: Configuration type" is informal for API documentation. Once merged, users won't benefit from knowing it's "new." A cleaner description would be simply "Instance type for scaling group members" or "Configuration type for instances."

cmd/cloud/instance.go (1)

172-182: Consider displaying instance type in the show command output.

The show command displays various instance details but omits the instance type. For consistency with the new feature, consider adding it:

Suggested addition
 fmt.Printf(fmtDetailRow, "Image:", inst.Image)
+fmt.Printf(fmtDetailRow, "Type:", inst.InstanceType) // After SDK Instance struct is updated
 fmt.Printf(fmtDetailRow, "Ports:", inst.Ports)

This depends on the SDK's Instance struct being updated to include InstanceType.

internal/repositories/postgres/instance_repo.go (1)

29-35: Consider normalizing empty instance_type to NULL on writes.

If instance_type is optional, storing empty strings can make “unset” values ambiguous. Using NULLIF keeps behavior consistent with your COALESCE reads and optional-field semantics.

♻️ Suggested change
-		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, NULLIF($11, '')::inet, $12, $13, $14, $15, $16)
+		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, NULLIF($11, '')::inet, $12, NULLIF($13, ''), $14, $15, $16)
...
-		SET name = $1, status = $2, version = version + 1, updated_at = $3, container_id = $4, ports = $5, vpc_id = $6, subnet_id = $7, private_ip = NULLIF($8, '')::inet, ovs_port = $9, instance_type = $10
+		SET name = $1, status = $2, version = version + 1, updated_at = $3, container_id = $4, ports = $5, vpc_id = $6, subnet_id = $7, private_ip = NULLIF($8, '')::inet, ovs_port = $9, instance_type = NULLIF($10, '')

Also applies to: 114-118

internal/repositories/postgres/migrations/075_add_instance_type.up.sql (1)

1-26: Consider adding a foreign key constraint for data integrity.

The instances.instance_type column references instance_types.id, but there's no foreign key constraint enforcing this relationship. This could allow invalid instance types to be stored.

🔧 Proposed fix to add foreign key constraint
 ALTER TABLE instances ADD COLUMN IF NOT EXISTS instance_type TEXT DEFAULT 'basic-2';
+
+-- Add foreign key after seeding instance_types to ensure referential integrity
+-- Note: Add this after the INSERT statements or use DEFERRABLE constraint

After the seed INSERT statements, add:

ALTER TABLE instances 
ADD CONSTRAINT fk_instances_instance_type 
FOREIGN KEY (instance_type) REFERENCES instance_types(id);

If the FK constraint is intentionally omitted (e.g., to allow custom/dynamic instance types not in the reference table), please confirm and document this design decision.

internal/core/services/instance_test.go (2)

20-24: Lift instance-type resource literals into named constants.

The VCPU/Memory numbers are repeated magic values; naming them improves intent and keeps the test data consistent.

♻️ Proposed constants
 const (
 	testPorts           = "8080:80"
 	testVPCNetwork      = "cloud-network"
 	defaultInstanceType = "basic-2"
+	defaultInstanceVCPUs    = 2
+	defaultInstanceMemoryMB = 4096
 )

-itRepo.On("GetByID", mock.Anything, mock.Anything).Return(&domain.InstanceType{ID: defaultInstanceType, VCPUs: 2, MemoryMB: 4096}, nil).Maybe()
+itRepo.On("GetByID", mock.Anything, mock.Anything).Return(&domain.InstanceType{ID: defaultInstanceType, VCPUs: defaultInstanceVCPUs, MemoryMB: defaultInstanceMemoryMB}, nil).Maybe()

-itRepo.On("GetByID", mock.Anything, mock.Anything).Return(&domain.InstanceType{ID: defaultInstanceType, VCPUs: 2, MemoryMB: 4096}, nil).Maybe()
+itRepo.On("GetByID", mock.Anything, mock.Anything).Return(&domain.InstanceType{ID: defaultInstanceType, VCPUs: defaultInstanceVCPUs, MemoryMB: defaultInstanceMemoryMB}, nil).Maybe()
As per coding guidelines: Do not use magic numbers - use named constants instead.

Also applies to: 101-101, 417-417


265-304: Convert Exec tests to a table-driven test.

These cases are structurally identical and fit the table-driven style required for tests.

♻️ Example table-driven rewrite
-func TestExecInstanceNotRunning(t *testing.T) {
-	repo, _, _, _, _, _, _, _, _, _, svc := setupInstanceServiceTest(t)
-	defer repo.AssertExpectations(t)
-
-	instID := uuid.New()
-	inst := &domain.Instance{ID: instID, ContainerID: ""}
-	repo.On("GetByID", mock.Anything, instID).Return(inst, nil)
-
-	_, err := svc.Exec(context.Background(), instID.String(), []string{"echo", "hi"})
-	assert.Error(t, err)
-}
-
-func TestExecInstanceSuccess(t *testing.T) {
-	repo, _, _, _, compute, _, _, _, _, _, svc := setupInstanceServiceTest(t)
-	defer repo.AssertExpectations(t)
-	defer compute.AssertExpectations(t)
-
-	instID := uuid.New()
-	inst := &domain.Instance{ID: instID, ContainerID: "c123"}
-	repo.On("GetByID", mock.Anything, instID).Return(inst, nil)
-	compute.On("Exec", mock.Anything, "c123", []string{"echo", "hi"}).Return("hi", nil)
-
-	out, err := svc.Exec(context.Background(), instID.String(), []string{"echo", "hi"})
-	assert.NoError(t, err)
-	assert.Equal(t, "hi", out)
-}
-
-func TestExecInstanceError(t *testing.T) {
-	repo, _, _, _, compute, _, _, _, _, _, svc := setupInstanceServiceTest(t)
-	defer repo.AssertExpectations(t)
-	defer compute.AssertExpectations(t)
-
-	instID := uuid.New()
-	inst := &domain.Instance{ID: instID, ContainerID: "c123"}
-	repo.On("GetByID", mock.Anything, instID).Return(inst, nil)
-	compute.On("Exec", mock.Anything, "c123", []string{"echo", "hi"}).Return("", assert.AnError)
-
-	_, err := svc.Exec(context.Background(), instID.String(), []string{"echo", "hi"})
-	assert.Error(t, err)
-}
+func TestExecInstance(t *testing.T) {
+	cases := []struct {
+		name        string
+		containerID string
+		execOut     string
+		execErr     error
+		wantErr     bool
+	}{
+		{name: "NotRunning", containerID: "", wantErr: true},
+		{name: "Success", containerID: "c123", execOut: "hi", wantErr: false},
+		{name: "ComputeError", containerID: "c123", execErr: assert.AnError, wantErr: true},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			repo, _, _, _, compute, _, _, _, _, _, svc := setupInstanceServiceTest(t)
+			defer repo.AssertExpectations(t)
+			if tc.containerID != "" {
+				defer compute.AssertExpectations(t)
+			}
+
+			instID := uuid.New()
+			inst := &domain.Instance{ID: instID, ContainerID: tc.containerID}
+			repo.On("GetByID", mock.Anything, instID).Return(inst, nil)
+			if tc.containerID != "" {
+				compute.On("Exec", mock.Anything, tc.containerID, []string{"echo", "hi"}).Return(tc.execOut, tc.execErr)
+			}
+
+			out, err := svc.Exec(context.Background(), instID.String(), []string{"echo", "hi"})
+			if tc.wantErr {
+				assert.Error(t, err)
+				return
+			}
+			assert.NoError(t, err)
+			assert.Equal(t, tc.execOut, out)
+		})
+	}
+}
As per coding guidelines: Use table-driven tests in test files.

Comment thread docs/api-reference.md
Comment on lines +176 to +189
### GET /instance-types
List all available instance types.
**Response:**
```json
[
{
"id": "basic-2",
"name": "Basic 2",
"vcpus": 1,
"memory_mb": 1024,
"disk_gb": 10
}
]
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n "InstanceType" --type go -A 15 internal/core/domain/instance_type.go

Repository: PoyrazK/thecloud

Length of output: 680


🏁 Script executed:

rg -n "instance-types|/instance-types" --type go -B 3 -A 15 | head -80

Repository: PoyrazK/thecloud

Length of output: 42


🏁 Script executed:

rg -n "GetInstanceTypes|instance.*types|InstanceType" --type go | grep -i "handler\|func\|route" | head -30

Repository: PoyrazK/thecloud

Length of output: 1798


🏁 Script executed:

rg -n "instance.*type|GET.*instance" --type go -B 2 -A 10 | grep -A 10 -B 2 "handler\|func.*Get\|router\|Route" | head -60

Repository: PoyrazK/thecloud

Length of output: 4465


🏁 Script executed:

fd -name "*.go" internal/ | xargs rg -l "instance-type\|InstanceType.*http\|GET" | head -10

Repository: PoyrazK/thecloud

Length of output: 290


🏁 Script executed:

fd "instance.*type.*handler" --type f internal/

Repository: PoyrazK/thecloud

Length of output: 42


🏁 Script executed:

rg "InstanceType.*\(\)" --type go -B 5 -A 15 internal/handlers/

Repository: PoyrazK/thecloud

Length of output: 42


🏁 Script executed:

fd "main.go|routes.go|server.go" --type f internal/ cmd/

Repository: PoyrazK/thecloud

Length of output: 139


🏁 Script executed:

rg "instance-types|GetInstanceTypes" --type go -B 3 -A 10 internal/

Repository: PoyrazK/thecloud

Length of output: 42


🏁 Script executed:

head -100 cmd/api/main.go

Repository: PoyrazK/thecloud

Length of output: 3843


🏁 Script executed:

rg -n "SetupRoutes\|router\|Route\|instance" --type go -A 5 cmd/api/main.go | head -50

Repository: PoyrazK/thecloud

Length of output: 42


🏁 Script executed:

fd "setup\|router" --type f internal/api/

Repository: PoyrazK/thecloud

Length of output: 42


🏁 Script executed:

cat internal/api/setup.go 2>/dev/null || cat internal/api/setup/setup.go 2>/dev/null || ls -la internal/api/

Repository: PoyrazK/thecloud

Length of output: 805


🏁 Script executed:

ls -la internal/api/setup/

Repository: PoyrazK/thecloud

Length of output: 628


🏁 Script executed:

rg "SetupRouter\|instance-types\|InstanceType" --type go internal/api/setup/

Repository: PoyrazK/thecloud

Length of output: 42


🏁 Script executed:

rg -n "instance-types\|instance_type\|InstanceType" --type go -B 2 -A 5 internal/api/setup/router.go

Repository: PoyrazK/thecloud

Length of output: 42


🏁 Script executed:

cat internal/api/setup/router.go | head -150

Repository: PoyrazK/thecloud

Length of output: 5987


🏁 Script executed:

grep -n "instance-types\|GET.*instance\|instance.*routes" internal/api/setup/router.go -A 5 -B 2

Repository: PoyrazK/thecloud

Length of output: 42


🏁 Script executed:

cat internal/api/setup/router.go | tail -n +150 | head -200

Repository: PoyrazK/thecloud

Length of output: 11017


🏁 Script executed:

grep -n "instance-types" internal/api/setup/router.go

Repository: PoyrazK/thecloud

Length of output: 42


🏁 Script executed:

rg "registerAdminRoutes\|registerDevOpsRoutes" --type go -A 100 internal/api/setup/router.go | head -150

Repository: PoyrazK/thecloud

Length of output: 42


🏁 Script executed:

wc -l internal/api/setup/router.go

Repository: PoyrazK/thecloud

Length of output: 92


🏁 Script executed:

tail -300 internal/api/setup/router.go

Repository: PoyrazK/thecloud

Length of output: 18842


🏁 Script executed:

sed -n '170,195p' docs/api-reference.md

Repository: PoyrazK/thecloud

Length of output: 456


🏁 Script executed:

rg "/instance-types|ListInstanceTypes|GetInstanceTypes" --type go -B 3 -A 10 internal/

Repository: PoyrazK/thecloud

Length of output: 42


🏁 Script executed:

rg "InstanceType.*List\|List.*InstanceType" --type go -B 2 -A 10 internal/handlers/

Repository: PoyrazK/thecloud

Length of output: 42


Document all InstanceType fields in the API response schema.

The InstanceType domain model includes network_mbps, price_per_hour, and category fields with JSON tags, so they will be serialized in API responses. Add these fields to the documented response schema for completeness.

🤖 Prompt for AI Agents
In `@docs/api-reference.md` around lines 176 - 189, The documented response for
GET /instance-types is missing fields present on the InstanceType model; update
the example JSON and schema to include network_mbps, price_per_hour, and
category so it matches the serialized API output. Locate the GET /instance-types
section and the InstanceType examples and add the network_mbps (number),
price_per_hour (number/string as used in serialization), and category (string)
properties to each instance object and any schema tables or descriptions
referencing InstanceType.

Comment thread internal/core/ports/instance_type.go
Comment thread internal/core/services/instance_test.go
Comment thread internal/core/services/instance.go
Comment thread internal/handlers/instance_handler.go
Comment thread internal/repositories/postgres/instance_type_repo.go
Comment thread pkg/sdk/compute.go
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@internal/core/services/instance_type.go`:
- Around line 10-21: Add unit tests for the instance type service by creating
internal/core/services/instance_type_test.go that exercises
NewInstanceTypeService and its List method: implement a mock of
ports.InstanceTypeRepository that you can configure to return a slice of
*domain.InstanceType and nil error (and a case returning an error), construct
the service with NewInstanceTypeService(mockRepo), call List(ctx) and assert the
returned results and errors match expectations; include tests for successful
listing and repository error propagation, and use context.Background() for calls
and table-driven style to keep tests concise.

In `@internal/handlers/instance_type_handler_test.go`:
- Line 54: The json.Unmarshal call currently discards its error; change it to
capture and assert the error instead of using the blank identifier—e.g., replace
the call to json.Unmarshal(data, &actualTypes) with err := json.Unmarshal(data,
&actualTypes) and then assert/check the error (use require.NoError(t, err) or
t.Fatalf/ t.Fatal if err != nil) so failures surface in the test; reference the
json.Unmarshal call and the actualTypes variable when making the change.
🧹 Nitpick comments (1)
internal/handlers/instance_type_handler_test.go (1)

29-59: Consider adding error case test and using table-driven structure.

The success case is well-tested. Consider adding:

  1. An error case when the service returns an error
  2. Table-driven test structure for better coverage
♻️ Suggested table-driven approach with error case
 func TestInstanceTypeHandlerList(t *testing.T) {
 	gin.SetMode(gin.TestMode)
-	mockSvc := new(instanceTypeServiceMock)
-	handler := NewInstanceTypeHandler(mockSvc)
-	r := gin.New()
-	r.GET("/instance-types", handler.List)
 
-	t.Run("success", func(t *testing.T) {
-		expectedTypes := []*domain.InstanceType{
-			{ID: "basic-1", Name: "Basic Small", VCPUs: 1, MemoryMB: 1024, DiskGB: 10},
-		}
-		mockSvc.On("List", mock.Anything).Return(expectedTypes, nil).Once()
+	tests := []struct {
+		name           string
+		mockReturn     []*domain.InstanceType
+		mockErr        error
+		expectedStatus int
+	}{
+		{
+			name:           "success",
+			mockReturn:     []*domain.InstanceType{{ID: "basic-1", Name: "Basic Small", VCPUs: 1, MemoryMB: 1024, DiskGB: 10}},
+			mockErr:        nil,
+			expectedStatus: http.StatusOK,
+		},
+		{
+			name:           "service error",
+			mockReturn:     nil,
+			mockErr:        errors.New(errors.Internal, "database error"),
+			expectedStatus: http.StatusInternalServerError,
+		},
+	}
 
-		req, _ := http.NewRequest(http.MethodGet, "/instance-types", nil)
-		w := httptest.NewRecorder()
-		r.ServeHTTP(w, req)
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			mockSvc := new(instanceTypeServiceMock)
+			handler := NewInstanceTypeHandler(mockSvc)
+			r := gin.New()
+			r.GET("/instance-types", handler.List)
 
-		assert.Equal(t, http.StatusOK, w.Code)
-		// ... rest of assertions
-		mockSvc.AssertExpectations(t)
-	})
+			mockSvc.On("List", mock.Anything).Return(tt.mockReturn, tt.mockErr).Once()
+
+			req, _ := http.NewRequest(http.MethodGet, "/instance-types", nil)
+			w := httptest.NewRecorder()
+			r.ServeHTTP(w, req)
+
+			assert.Equal(t, tt.expectedStatus, w.Code)
+			mockSvc.AssertExpectations(t)
+		})
+	}
 }

Comment on lines +10 to +21
type instanceTypeService struct {
repo ports.InstanceTypeRepository
}

// NewInstanceTypeService creates a new InstanceTypeService.
func NewInstanceTypeService(repo ports.InstanceTypeRepository) ports.InstanceTypeService {
return &instanceTypeService{repo: repo}
}

func (s *instanceTypeService) List(ctx context.Context) ([]*domain.InstanceType, error) {
return s.repo.List(ctx)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for test files related to instance_type service
fd -t f 'instance_type.*test\.go' internal/core/services/
fd -t f '.*_test\.go' internal/core/services/ | xargs grep -l 'instanceTypeService\|NewInstanceTypeService\|InstanceTypeService' 2>/dev/null || echo "No matches found"

Repository: PoyrazK/thecloud

Length of output: 76


Service implementation is structurally sound, but tests are required.

The code correctly:

  • Uses an unexported struct with exported constructor
  • Imports only ports and domain packages
  • Delegates to repository via the port interface
  • Passes context properly

However, per coding guidelines, new services must include tests. Create internal/core/services/instance_type_test.go with test coverage for List() method using mocked repositories.

🤖 Prompt for AI Agents
In `@internal/core/services/instance_type.go` around lines 10 - 21, Add unit tests
for the instance type service by creating
internal/core/services/instance_type_test.go that exercises
NewInstanceTypeService and its List method: implement a mock of
ports.InstanceTypeRepository that you can configure to return a slice of
*domain.InstanceType and nil error (and a case returning an error), construct
the service with NewInstanceTypeService(mockRepo), call List(ctx) and assert the
returned results and errors match expectations; include tests for successful
listing and repository error propagation, and use context.Background() for calls
and table-driven style to keep tests concise.

Comment thread internal/handlers/instance_type_handler_test.go Outdated
@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/core/services/instance_test.go (1)

528-537: ⚠️ Potential issue | 🟡 Minor

Remove incomplete test stub or implement it properly with assertions.

The test sets up a mock but never invokes any code or asserts results, so it always passes without validation. Since getVolumeByIDOrName is unexported and only called by resolveVolumes (which is called by LaunchInstance), test volume resolution through LaunchInstance instead—similar to the existing TestInstanceServiceLaunchWithVolumes test, which properly invokes the service and asserts behavior using table-driven test cases per coding guidelines. Either implement this test with actual invocations and assertions or remove it.

🤖 Fix all issues with AI agents
In `@internal/core/services/instance.go`:
- Around line 98-105: The instance-type lookup currently maps all errors from
s.instanceTypeRepo.GetByID to errors.InvalidInput; change this to distinguish
not-found from internal failures: after calling s.instanceTypeRepo.GetByID(ctx,
instanceType) check whether the returned error represents a not-found (e.g.,
errors.Is(err, repo.ErrNotFound) or errors.Is(err, sql.ErrNoRows) or a
repository-provided IsNotFound helper) and only then return errors.InvalidInput
with the "invalid instance type" message; for any other non-nil error return an
internal/server error (e.g., errors.Internal) preserving or wrapping the
original error for logging/diagnostics. Ensure you reference the same symbols
s.instanceTypeRepo.GetByID, instanceType, and the error constructors
(errors.InvalidInput / errors.Internal) when making this change.
🧹 Nitpick comments (4)
pkg/sdk/compute.go (1)

51-60: Optionally omit instance_type when empty.
This avoids sending an empty string and lets the server default cleanly.

♻️ Suggested adjustment
 body := map[string]interface{}{
 	"name":          name,
 	"image":         image,
 	"ports":         ports,
-	"instance_type": instanceType,
 	"vpc_id":        vpcID,
 	"subnet_id":     subnetID,
 	"volumes":       volumes,
 }
+if instanceType != "" {
+	body["instance_type"] = instanceType
+}
internal/repositories/noop/adapters.go (1)

42-51: Replace magic numbers with named constants for noop instance types.
This keeps test fixtures self-describing and consistent.

♻️ Suggested adjustment
 func (r *NoopInstanceTypeRepository) List(ctx context.Context) ([]*domain.InstanceType, error) {
+	const (
+		basic1VCPUs    = 1
+		basic1MemoryMB = 512
+		basic1DiskGB   = 8
+		basic2VCPUs    = 1
+		basic2MemoryMB = 1024
+		basic2DiskGB   = 10
+	)
 	return []*domain.InstanceType{
-		{ID: "basic-1", Name: "Basic 1", VCPUs: 1, MemoryMB: 512, DiskGB: 8},
-		{ID: "basic-2", Name: "Basic 2", VCPUs: 1, MemoryMB: 1024, DiskGB: 10},
+		{ID: "basic-1", Name: "Basic 1", VCPUs: basic1VCPUs, MemoryMB: basic1MemoryMB, DiskGB: basic1DiskGB},
+		{ID: "basic-2", Name: "Basic 2", VCPUs: basic2VCPUs, MemoryMB: basic2MemoryMB, DiskGB: basic2DiskGB},
 	}, nil
 }
As per coding guidelines: Do not use magic numbers - use named constants instead.
internal/core/services/instance.go (1)

175-177: Use named constants for byte multipliers.
This improves readability and aligns with the no-magic-numbers guideline.

♻️ Suggested adjustment
-	cpuLimit := int64(it.VCPUs)
-	memLimit := int64(it.MemoryMB) * 1024 * 1024
-	diskLimit := int64(it.DiskGB) * 1024 * 1024 * 1024
+	const (
+		bytesPerMB = 1024 * 1024
+		bytesPerGB = 1024 * 1024 * 1024
+	)
+	cpuLimit := int64(it.VCPUs)
+	memLimit := int64(it.MemoryMB) * bytesPerMB
+	diskLimit := int64(it.DiskGB) * bytesPerGB
As per coding guidelines: Do not use magic numbers - use named constants instead.
internal/handlers/instance_handler.go (1)

41-47: Add omitempty to optional request fields.

Ports, VpcID, SubnetID, and Volumes are optional (no binding:"required"), so their JSON tags should include omitempty to match the project convention. As per coding guidelines: Include json tags on struct fields with omitempty for optional fields.

♻️ Suggested change
-	Ports        string                    `json:"ports"`
-	VpcID        string                    `json:"vpc_id"`
-	SubnetID     string                    `json:"subnet_id"`
-	Volumes      []VolumeAttachmentRequest `json:"volumes"`
+	Ports        string                    `json:"ports,omitempty"`
+	VpcID        string                    `json:"vpc_id,omitempty"`
+	SubnetID     string                    `json:"subnet_id,omitempty"`
+	Volumes      []VolumeAttachmentRequest `json:"volumes,omitempty"`

Comment on lines +98 to +105
// 2. Resolve Instance Type
if instanceType == "" {
instanceType = "basic-2"
}
_, err = s.instanceTypeRepo.GetByID(ctx, instanceType)
if err != nil {
return nil, errors.New(errors.InvalidInput, fmt.Sprintf("invalid instance type: %s", instanceType))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t map all instance-type lookup failures to InvalidInput.
If the repo fails (e.g., DB outage), callers will see a 400 instead of an internal error and may not retry. Distinguish NotFound vs internal failures.

🛠️ Suggested handling
-import (
+import (
+	stderrors "errors"
 	"context"
 	"encoding/json"
 	"fmt"
 	"io"
 	_, err = s.instanceTypeRepo.GetByID(ctx, instanceType)
 	if err != nil {
-		return nil, errors.New(errors.InvalidInput, fmt.Sprintf("invalid instance type: %s", instanceType))
+		var derr errors.Error
+		if stderrors.As(err, &derr) && derr.Type == errors.NotFound {
+			return nil, errors.New(errors.InvalidInput, fmt.Sprintf("invalid instance type: %s", instanceType))
+		}
+		return nil, errors.Wrap(errors.Internal, "failed to resolve instance type", err)
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 2. Resolve Instance Type
if instanceType == "" {
instanceType = "basic-2"
}
_, err = s.instanceTypeRepo.GetByID(ctx, instanceType)
if err != nil {
return nil, errors.New(errors.InvalidInput, fmt.Sprintf("invalid instance type: %s", instanceType))
}
// 2. Resolve Instance Type
if instanceType == "" {
instanceType = "basic-2"
}
_, err = s.instanceTypeRepo.GetByID(ctx, instanceType)
if err != nil {
var derr errors.Error
if stderrors.As(err, &derr) && derr.Type == errors.NotFound {
return nil, errors.New(errors.InvalidInput, fmt.Sprintf("invalid instance type: %s", instanceType))
}
return nil, errors.Wrap(errors.Internal, "failed to resolve instance type", err)
}
🤖 Prompt for AI Agents
In `@internal/core/services/instance.go` around lines 98 - 105, The instance-type
lookup currently maps all errors from s.instanceTypeRepo.GetByID to
errors.InvalidInput; change this to distinguish not-found from internal
failures: after calling s.instanceTypeRepo.GetByID(ctx, instanceType) check
whether the returned error represents a not-found (e.g., errors.Is(err,
repo.ErrNotFound) or errors.Is(err, sql.ErrNoRows) or a repository-provided
IsNotFound helper) and only then return errors.InvalidInput with the "invalid
instance type" message; for any other non-nil error return an internal/server
error (e.g., errors.Internal) preserving or wrapping the original error for
logging/diagnostics. Ensure you reference the same symbols
s.instanceTypeRepo.GetByID, instanceType, and the error constructors
(errors.InvalidInput / errors.Internal) when making this change.

@poyrazK poyrazK merged commit 100cf9d into main Jan 30, 2026
26 checks passed
@poyrazK poyrazK deleted the feature/instance-types branch February 9, 2026 22:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant