mirror of
https://github.com/lukaszraczylo/kportal.git
synced 2026-06-12 00:19:24 +00:00
96ae1d45e0
- [x] Add golangci-lint configuration with gocritic ifElseChain disabled
- [x] Rename error variables to avoid shadowing (createErr, watcherErr, watchErr, etc.)
- [x] Replace `interface{}` with `any` type alias throughout codebase
- [x] Add package-level documentation comments to all internal packages
- [x] Reorder struct fields alphabetically for consistency
- [x] Extract UI constants (terminal dimensions, column widths, colors) to constants.go
- [x] Refactor BubbleTeaUI main view rendering into smaller helper functions
- [x] Simplify nested conditionals and improve code clarity
- [x] Add `isForwardDisabled()` helper method to BubbleTeaUI
- [x] Update file permissions from 0644 to 0600 in config tests
- [x] Add `#nosec` comments and error suppression where appropriate
- [x] Improve test table struct field ordering for readability
- [x] Fix resource parsing in AddForward using strings.SplitN
- [x] Add comprehensive tests for new UI helper functions and constants
214 lines
5.8 KiB
Go
214 lines
5.8 KiB
Go
// Package converter provides configuration migration from other port-forwarding
|
|
// tools to kportal's YAML format. Currently supports kftray JSON format.
|
|
//
|
|
// Basic usage:
|
|
//
|
|
// err := converter.ConvertKFTrayToKPortal("kftray.json", ".kportal.yaml")
|
|
// if err != nil {
|
|
// log.Fatal(err)
|
|
// }
|
|
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"`
|
|
Context string `json:"context"`
|
|
WorkloadType string `json:"workload_type"`
|
|
Protocol string `json:"protocol"`
|
|
Alias string `json:"alias"`
|
|
LocalPort int `json:"local_port"`
|
|
RemotePort int `json:"remote_port"`
|
|
}
|
|
|
|
// ConvertKFTrayToKPortal converts kftray JSON configuration to kportal YAML format
|
|
func ConvertKFTrayToKPortal(inputFile, outputFile string) error {
|
|
// Read kftray JSON config
|
|
// #nosec G304 -- inputFile is from command line argument for explicit conversion
|
|
data, err := os.ReadFile(inputFile)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read input file: %w", err)
|
|
}
|
|
|
|
var kftrayConfigs []KFTrayConfig
|
|
if unmarshalErr := json.Unmarshal(data, &kftrayConfigs); unmarshalErr != nil {
|
|
return fmt.Errorf("failed to parse JSON: %w", unmarshalErr)
|
|
}
|
|
|
|
// 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) {
|
|
// #nosec G304 -- inputFile is from command line argument for explicit conversion
|
|
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"`
|
|
Alias string `yaml:"alias,omitempty"`
|
|
Port int `yaml:"port"`
|
|
LocalPort int `yaml:"localPort"`
|
|
}
|
|
|
|
// 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
|
|
}
|