Files
kportal/internal/converter/kftray.go
T
lukaszraczylo 23cd45a3d7 improvements nov2025 pt2 (#13)
* Further improvements

| Fix                                | Impact                                 | Files Modified                       |
|------------------------------------|----------------------------------------|--------------------------------------|
| sync.Pool for health check buffers | Reduces GC pressure ~30%               | internal/healthcheck/checker.go      |
| Goroutine leak fix + sync.Once     | Prevents memory leaks                  | internal/forward/worker.go           |
| Cache eviction for expired entries | Prevents unbounded memory growth       | internal/k8s/resolver.go             |
| Backoff reset on success           | Faster recovery after long connections | internal/forward/worker.go           |
| Converter file permissions         | Security hardening (0644→0600)         | internal/converter/kftray.go         |
| HTTP body size limiting            | Prevents OOM with large requests       | internal/httplog/proxy.go, logger.go |
| WaitGroup for config watcher       | Clean goroutine shutdown               | internal/config/watcher.go           |
| Signal handler cleanup             | Ensures all resources released         | cmd/kportal/main.go                  |

* Additional event bus for internal event handling

| Metric                 | Before                                | After             | Improvement        |
|------------------------|---------------------------------------|-------------------|--------------------|
| Goroutines per forward | 3 (worker + heartbeat + health check) | 1 (worker only)   | 66% reduction      |
| Tickers per forward    | 2 (heartbeat + health check)          | 0                 | 100% reduction     |
| Global goroutines      | 2 (watchdog + health monitor)         | 2                 | Same               |
| Lock acquisitions/sec  | O(n) per interval                     | O(1) per interval | Linear improvement |


* Add UI testing
* Add mocks
* Add more logs and details to be displayed
2025-11-26 13:18:50 +00:00

203 lines
5.3 KiB
Go

package converter
import (
"encoding/json"
"fmt"
"os"
"sort"
"github.com/nvm/kportal/internal/config"
"gopkg.in/yaml.v3"
)
// KFTrayConfig represents a single port-forward entry from kftray JSON format
type KFTrayConfig struct {
Service string `json:"service"`
Namespace string `json:"namespace"`
LocalPort int `json:"local_port"`
RemotePort int `json:"remote_port"`
Context string `json:"context"`
WorkloadType string `json:"workload_type"`
Protocol string `json:"protocol"`
Alias string `json:"alias"`
}
// ConvertKFTrayToKPortal converts kftray JSON configuration to kportal YAML format
func ConvertKFTrayToKPortal(inputFile, outputFile string) error {
// Read kftray JSON config
data, err := os.ReadFile(inputFile)
if err != nil {
return fmt.Errorf("failed to read input file: %w", err)
}
var kftrayConfigs []KFTrayConfig
if err := json.Unmarshal(data, &kftrayConfigs); err != nil {
return fmt.Errorf("failed to parse JSON: %w", err)
}
// Convert to kportal format
kportalConfig := convertToKPortal(kftrayConfigs)
// Write kportal YAML config
yamlData, err := yaml.Marshal(kportalConfig)
if err != nil {
return fmt.Errorf("failed to generate YAML: %w", err)
}
// Add header comment
header := "# kportal configuration converted from kftray format\n# Generated by kportal --convert\n\n"
yamlData = append([]byte(header), yamlData...)
if err := os.WriteFile(outputFile, yamlData, 0600); err != nil {
return fmt.Errorf("failed to write output file: %w", err)
}
return nil
}
// GetConversionSummary returns statistics about the kftray configuration
func GetConversionSummary(inputFile string) (map[string]map[string]int, int, error) {
data, err := os.ReadFile(inputFile)
if err != nil {
return nil, 0, fmt.Errorf("failed to read input file: %w", err)
}
var kftrayConfigs []KFTrayConfig
if err := json.Unmarshal(data, &kftrayConfigs); err != nil {
return nil, 0, fmt.Errorf("failed to parse JSON: %w", err)
}
contextMap := make(map[string]map[string]int)
for _, cfg := range kftrayConfigs {
if _, ok := contextMap[cfg.Context]; !ok {
contextMap[cfg.Context] = make(map[string]int)
}
contextMap[cfg.Context][cfg.Namespace]++
}
return contextMap, len(kftrayConfigs), nil
}
func convertToKPortal(kftrayConfigs []KFTrayConfig) config.Config {
// Group by context and namespace
contextMap := make(map[string]map[string][]forwardEntry)
for _, cfg := range kftrayConfigs {
// Initialize context if not exists
if _, ok := contextMap[cfg.Context]; !ok {
contextMap[cfg.Context] = make(map[string][]forwardEntry)
}
// Build resource string based on workload type
resource := fmt.Sprintf("%s/%s", cfg.WorkloadType, cfg.Service)
// Create forward entry
forward := forwardEntry{
Resource: resource,
Protocol: cfg.Protocol,
Port: cfg.RemotePort,
LocalPort: cfg.LocalPort,
Alias: cfg.Alias,
}
// Add to namespace
contextMap[cfg.Context][cfg.Namespace] = append(
contextMap[cfg.Context][cfg.Namespace],
forward,
)
}
// Convert map to structured config
var contexts []contextEntry
// Sort contexts for consistent output
contextNames := make([]string, 0, len(contextMap))
for name := range contextMap {
contextNames = append(contextNames, name)
}
sort.Strings(contextNames)
for _, contextName := range contextNames {
namespaceMap := contextMap[contextName]
// Sort namespaces
namespaceNames := make([]string, 0, len(namespaceMap))
for name := range namespaceMap {
namespaceNames = append(namespaceNames, name)
}
sort.Strings(namespaceNames)
var namespaces []namespaceEntry
for _, namespaceName := range namespaceNames {
forwards := namespaceMap[namespaceName]
// Sort forwards by local port for consistent output
sort.Slice(forwards, func(i, j int) bool {
return forwards[i].LocalPort < forwards[j].LocalPort
})
namespaces = append(namespaces, namespaceEntry{
Name: namespaceName,
Forwards: forwards,
})
}
contexts = append(contexts, contextEntry{
Name: contextName,
Namespaces: namespaces,
})
}
return config.Config{
Contexts: convertToConfigContexts(contexts),
}
}
// Internal types for conversion (to avoid circular dependencies)
type contextEntry struct {
Name string
Namespaces []namespaceEntry
}
type namespaceEntry struct {
Name string
Forwards []forwardEntry
}
type forwardEntry struct {
Resource string `yaml:"resource"`
Protocol string `yaml:"protocol"`
Port int `yaml:"port"`
LocalPort int `yaml:"localPort"`
Alias string `yaml:"alias,omitempty"`
}
// Convert internal types to config package types
func convertToConfigContexts(contexts []contextEntry) []config.Context {
var result []config.Context
for _, ctx := range contexts {
var namespaces []config.Namespace
for _, ns := range ctx.Namespaces {
var forwards []config.Forward
for _, fwd := range ns.Forwards {
forwards = append(forwards, config.Forward{
Resource: fwd.Resource,
Protocol: fwd.Protocol,
Port: fwd.Port,
LocalPort: fwd.LocalPort,
Alias: fwd.Alias,
})
}
namespaces = append(namespaces, config.Namespace{
Name: ns.Name,
Forwards: forwards,
})
}
result = append(result, config.Context{
Name: ctx.Name,
Namespaces: namespaces,
})
}
return result
}