Skip to content

chore: cursor rules #2393

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 30 additions & 0 deletions .cursor/rules/clean-code.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
description:
globs:
alwaysApply: false
---
# Clean Code

Essential clean code principles for Go development in Embedded Cluster.

## File Formatting

### End of File
- **Always leave a single newline at the end of every file**
- This ensures proper POSIX compliance and clean git diffs

## Comments

### Keep Comments Concise
- **Comments should be brief and to the point**
- Explain *why*, not *what* the code does
- Avoid redundant comments that just repeat the code

### Function Comments
- Use concise godoc format for exported functions
- Focus on purpose and important behavior
- Single line comments for simple functions

### Inline Comments
- Use sparingly for complex logic
- Keep on same line when possible for short explanations
253 changes: 253 additions & 0 deletions .cursor/rules/ec-config-types.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
---
description:
globs:
alwaysApply: false
---
# Installation Config vs Runtime Config

Quick reference and actionable rules for working with Installation Config and Runtime Config in Embedded Cluster.

> **📖 Comprehensive Documentation**: See `/design/installation-vs-runtime-config.md` for detailed architecture and understanding.

## Quick Reference

### When to Use Each Type

| Aspect | Installation Config | Runtime Config |
|--------|-------------------|----------------|
| **Purpose** | User input and preferences | System state and environment |
| **Data Source** | User-provided values | Derived from installation config |
| **Persistence** | In-memory during installation | Disk-based (`/etc/embedded-cluster/ec.yaml`) |
| **Lifecycle** | Installation process only | Entire cluster lifetime |
| **Validation** | User-facing error messages | System constraint checking |
| **Responsibilities** | Input validation, user preferences | Environment variables, file paths |

### Data Flow
```
User Input → Installation Config → Runtime Config → System Environment
```

## Code Patterns

### Adding User-Configurable Options

#### 1. Add to Installation Config
```go
// Add field to types.InstallationConfig
type InstallationConfig struct {
// ... existing fields ...
NewUserOption string `json:"newUserOption"`
}

// Add validation in installation manager
func (m *InstallationManager) ValidateConfig(config types.InstallationConfig) error {
if config.NewUserOption == "" {
return types.NewAPIError(types.ErrValidation, "newUserOption is required")
}
// Validate user-facing constraints
if !isValidUserOption(config.NewUserOption) {
return types.NewAPIError(types.ErrValidation, "invalid user option format")
}
return nil
}
```

#### 2. Update Runtime Config from Installation Config
```go
// Add system-level handling in runtime config
func (c *Controller) updateRuntimeFromInstallation(installConfig types.InstallationConfig) error {
// Transform user preference to system configuration
c.rc.SetNewSystemOption(installConfig.NewUserOption)

// Apply to environment
if err := c.rc.SetEnv(); err != nil {
return fmt.Errorf("failed to set environment: %w", err)
}

// Persist system state
if err := c.rc.WriteToDisk(); err != nil {
return fmt.Errorf("failed to persist runtime config: %w", err)
}

return nil
}
```

### Configuration Update Flow

#### Complete Update Pattern
```go
func (c *Controller) UpdateInstallationConfig(config types.InstallationConfig) error {
// Step 1: Validate user input (Installation Config responsibility)
if err := c.installationManager.ValidateConfig(config); err != nil {
return fmt.Errorf("invalid configuration: %w", err)
}

// Step 2: Store installation config (temporary, in-memory)
if err := c.installationManager.SetConfig(config); err != nil {
return fmt.Errorf("failed to save config: %w", err)
}

// Step 3: Transform to runtime config (system state)
c.rc.SetDataDir(config.DataDirectory)
c.rc.SetAdminConsolePort(config.AdminConsolePort)
c.rc.SetLocalArtifactMirrorPort(config.LocalArtifactMirrorPort)

// Step 4: Apply environment changes (Runtime Config responsibility)
if err := c.rc.SetEnv(); err != nil {
return fmt.Errorf("failed to set environment: %w", err)
}

// Step 5: Persist runtime state (permanent, disk-based)
if err := c.rc.WriteToDisk(); err != nil {
return fmt.Errorf("failed to persist runtime config: %w", err)
}

return nil
}
```

### Validation Patterns

#### Installation Config Validation (User-Facing)
```go
func (m *InstallationManager) ValidateConfig(config types.InstallationConfig) error {
// Port validation with user-friendly messages
if config.AdminConsolePort < 1024 {
return types.NewAPIError(types.ErrValidation,
"admin console port must be 1024 or higher")
}

if isPortInUse(config.AdminConsolePort) {
return types.NewAPIError(types.ErrValidation,
fmt.Sprintf("port %d is already in use", config.AdminConsolePort))
}

// Network validation
if !isValidCIDR(config.GlobalCIDR) {
return types.NewAPIError(types.ErrValidation,
fmt.Sprintf("invalid network CIDR: %s", config.GlobalCIDR))
}

// Path validation
if config.DataDirectory == "" {
return types.NewAPIError(types.ErrValidation,
"data directory is required")
}

return nil
}
```

#### Runtime Config Validation (System Constraints)
```go
func (rc *RuntimeConfig) SetDataDir(dir string) error {
// System-level validation
if dir == "" {
return fmt.Errorf("data directory cannot be empty")
}

// Ensure directory can be created
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("failed to create directory %s: %w", dir, err)
}

// Check write permissions
if !isWritableDir(dir) {
return fmt.Errorf("directory not writable: %s", dir)
}

rc.spec.DataDirectory = dir
return nil
}
```

## Testing Patterns

### Installation Config Tests (User Input Focus)
```go
func TestInstallationConfigValidation(t *testing.T) {
tests := []struct {
name string
config types.InstallationConfig
wantErr bool
errMsg string
}{
{
name: "valid config",
config: types.InstallationConfig{
AdminConsolePort: 30000,
DataDirectory: "/tmp/test",
GlobalCIDR: "10.244.0.0/16",
},
wantErr: false,
},
{
name: "invalid port - too low",
config: types.InstallationConfig{
AdminConsolePort: 80,
},
wantErr: true,
errMsg: "port must be 1024 or higher",
},
{
name: "invalid CIDR",
config: types.InstallationConfig{
GlobalCIDR: "invalid-cidr",
},
wantErr: true,
errMsg: "invalid network CIDR",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := manager.ValidateConfig(tt.config)
if tt.wantErr {
require.Error(t, err)
assert.Contains(t, err.Error(), tt.errMsg)
} else {
require.NoError(t, err)
}
})
}
}
```

### Runtime Config Tests (System State Focus)
```go
func TestRuntimeConfigPersistence(t *testing.T) {
tempDir := t.TempDir()

// Create runtime config with system state
rc := runtimeconfig.New(ecv1beta1.RuntimeConfigSpec{
DataDirectory: tempDir,
AdminConsolePort: 30000,
})

// Test persistence
err := rc.WriteToDisk()
require.NoError(t, err)

// Test loading
loaded, err := runtimeconfig.NewFromDisk()
require.NoError(t, err)

// Verify system state preserved
assert.Equal(t, tempDir, loaded.DataDirectory())
assert.Equal(t, 30000, loaded.AdminConsolePort())
}

func TestRuntimeConfigEnvironment(t *testing.T) {
rc := runtimeconfig.New(ecv1beta1.RuntimeConfigSpec{
AdminConsolePort: 30000,
})

// Test environment variable application
err := rc.SetEnv()
require.NoError(t, err)

// Verify environment variables set
assert.Equal(t, "30000", os.Getenv("ADMIN_CONSOLE_PORT"))
}
```
Loading
Loading