style: Extract UI constants and refactor main view rendering (#30)

- [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
This commit is contained in:
2026-01-13 09:37:45 +00:00
committed by GitHub
parent 3d71f64901
commit 96ae1d45e0
54 changed files with 1319 additions and 730 deletions
+17 -6
View File
@@ -1,3 +1,14 @@
// Package k8s provides Kubernetes client management, resource resolution,
// and port-forwarding capabilities for kportal.
//
// Key components:
// - ClientPool: Thread-safe management of Kubernetes clients per context
// - ResourceResolver: Resolves pod/service/selector targets to actual pods
// - PortForwarder: Establishes and manages port-forward connections
// - Discovery: Provides resource discovery for the UI wizards
//
// The package handles automatic pod restart detection through re-resolution,
// caching with 30-second TTL, and graceful connection management.
package k8s
import (
@@ -12,10 +23,10 @@ import (
// ClientPool manages Kubernetes clients per context with thread-safe access.
type ClientPool struct {
mu sync.RWMutex
loader clientcmd.ClientConfig
clients map[string]*kubernetes.Clientset
configs map[string]*rest.Config
loader clientcmd.ClientConfig
mu sync.RWMutex
}
// NewClientPool creates a new ClientPool instance.
@@ -51,8 +62,8 @@ func (p *ClientPool) GetClient(contextName string) (*kubernetes.Clientset, error
defer p.mu.Unlock()
// Double-check in case another goroutine created it while we waited
if client, exists := p.clients[contextName]; exists {
return client, nil
if cachedClient, ok := p.clients[contextName]; ok {
return cachedClient, nil
}
// Create new client
@@ -91,8 +102,8 @@ func (p *ClientPool) GetRestConfig(contextName string) (*rest.Config, error) {
defer p.mu.Unlock()
// Double-check in case another goroutine created it while we waited
if config, exists := p.configs[contextName]; exists {
return config, nil
if cachedConfig, ok := p.configs[contextName]; ok {
return cachedConfig, nil
}
// Create new config
+2 -2
View File
@@ -146,8 +146,8 @@ func TestClientPool_ThreadSafety(t *testing.T) {
go func() {
pool.ClearCache()
pool.RemoveContext("test-context")
pool.GetCurrentContext()
pool.ListContexts()
_, _ = pool.GetCurrentContext()
_, _ = pool.ListContexts()
done <- true
}()
}
+5 -5
View File
@@ -28,11 +28,11 @@ func NewDiscovery(pool *ClientPool) *Discovery {
// PodInfo contains information about a pod relevant for port forwarding.
type PodInfo struct {
Created metav1.Time
Name string
Namespace string
Containers []ContainerInfo
Status string
Created metav1.Time
Containers []ContainerInfo
}
// ContainerInfo contains information about a container within a pod.
@@ -44,17 +44,17 @@ type ContainerInfo struct {
// PortInfo describes a port exposed by a container or service.
type PortInfo struct {
Name string
Port int32
TargetPort int32 // For services: the actual pod port to forward to
Protocol string
Port int32
TargetPort int32
}
// ServiceInfo contains information about a service.
type ServiceInfo struct {
Name string
Namespace string
Ports []PortInfo
Type string
Ports []PortInfo
}
// ListContexts returns all available Kubernetes contexts from kubeconfig.
+3 -3
View File
@@ -14,12 +14,12 @@ import (
func TestResolveTargetPort(t *testing.T) {
tests := []struct {
name string
servicePort corev1.ServicePort
service *corev1.Service
name string
description string
servicePort corev1.ServicePort
pods []corev1.Pod
expectedPort int32
description string
}{
{
name: "numeric targetPort",
+8 -8
View File
@@ -49,16 +49,16 @@ func (pf *PortForwarder) SetDialTimeout(timeout time.Duration) {
// ForwardRequest contains the parameters for a port-forward request.
type ForwardRequest struct {
ContextName string // Kubernetes context name
Namespace string // Namespace
Resource string // Resource (pod/name or service/name)
Selector string // Label selector (for pod resolution)
LocalPort int // Local port
RemotePort int // Remote port
Out io.Writer
ErrOut io.Writer
StopChan chan struct{}
ReadyChan chan struct{}
Out io.Writer // Output writer for logs
ErrOut io.Writer // Error output writer
ContextName string
Namespace string
Resource string
Selector string
LocalPort int
RemotePort int
}
// Forward establishes a port-forward connection to a Kubernetes resource.
+5 -5
View File
@@ -19,15 +19,15 @@ const (
// ResolvedResource represents a resolved Kubernetes resource.
type ResolvedResource struct {
Name string // The resolved pod or service name
Namespace string // The namespace
Timestamp time.Time // When this was resolved
Timestamp time.Time
Name string
Namespace string
}
// cacheEntry stores a cached resolution result with expiry.
type cacheEntry struct {
resource ResolvedResource
expiresAt time.Time
resource ResolvedResource
}
// ResourceResolver resolves Kubernetes resources with caching.
@@ -188,7 +188,7 @@ func (r *ResourceResolver) getFromCache(key string) string {
// Upgrade to write lock and delete expired entry
r.cacheMu.Lock()
// Double-check entry still exists and is still expired (may have been updated)
if entry, exists := r.cache[key]; exists && time.Now().After(entry.expiresAt) {
if expiredEntry, ok := r.cache[key]; ok && time.Now().After(expiredEntry.expiresAt) {
delete(r.cache, key)
}
r.cacheMu.Unlock()