Add flag registry for CLI commands

Signed-off-by: yxxhero <aiopsclub@163.com>
This commit is contained in:
Hubertbits 2025-04-16 15:41:43 +02:00 committed by yxxhero
parent 8067504097
commit 6b587e15f0
18 changed files with 770 additions and 248 deletions

77
pkg/flags/README.md Normal file
View File

@ -0,0 +1,77 @@
# Flags Package
## Overview
The `pkg/flags` package provides utilities for registering, handling, and transferring command-line flags. It serves as the bridge between the command-line interface and the configuration options.
## File Structure
```
pkg/flags/
├── README.md # This documentation file
├── registry.go # Core interface and generic implementation
├── registry_mock.go # Mock implementation for testing
├── registry_diff.go # Diff-specific registry
├── registry_apply.go # Apply-specific registry
├── registry_sync.go # Sync-specific registry
├── registry_template.go # Template-specific registry
├── registry_lint.go # Lint-specific registry
├── registry_destroy.go # Destroy-specific registry
├── registry_fetch.go # Fetch-specific registry
├── registry_list.go # List-specific registry
├── registry_status.go # Status-specific registry
├── registry_test.go # Tests for registry implementation
├── flag_handler.go # FlagHandler interface
├── flag_handler_mock.go # Mock implementation of FlagHandler
├── flag_handler_test.go # Tests for flag handler implementations
├── flag_value.go # Generic flag value retrieval functions
└── flag_value_test.go # Tests for flag value functions
```
## Components
- **FlagHandler Interface**: Defines how components handle flag values with boolean return for success
- **FlagRegistry**: Manages flag registration and transfer
- **Command-specific Registries**: Specialized registries for each command
- **Helper Functions**: Utilities for getting flag values of different types
## Key Features
- **Type Safety**: Helper functions for safely retrieving typed flag values
- **Flag Registration**: Centralized registration of flags to command objects
- **Flag Transfer**: Mechanism to transfer flag values to option objects
- **Flag Existence Checking**: Methods to check if flags are registered or handled
- **Success Indication**: Boolean return values to indicate if flags were successfully handled
## Usage
```go
// Create a registry
registry := flags.NewGenericFlagRegistry()
// Register flags to a command
registry.RegisterFlags(cmd)
// Transfer flags to options
registry.TransferFlags(cmd, opts)
// Check if a flag is registered
if registry.IsFlagRegistered("my-flag") {
// Do something
}
// Handle a flag with success indication
handled := opts.HandleFlag("flag-name", value, changed)
if !handled {
// Flag wasn't recognized
}
```
## Testing
The package includes mock implementations for testing:
- `MockFlagHandler`: For testing flag handling logic
- `MockFlagRegistry`: For testing flag registration and transfer
These mocks can be used to verify flag handling behavior without needing real command objects.

View File

@ -1,49 +0,0 @@
package flags
import (
"github.com/spf13/cobra"
)
// FlagRegistrar defines an interface for registering and transferring flags
type FlagRegistrar interface {
RegisterFlags(cmd *cobra.Command)
TransferFlags(cmd *cobra.Command, opts interface{})
}
// FlagHandler is a generic interface for handling flag values
type FlagHandler interface {
// HandleFlag receives a flag name, value, and whether it was changed
HandleFlag(name string, value interface{}, changed bool)
}
// GenericFlagRegistrar is a base struct for flag registrars
type GenericFlagRegistrar struct {
// Map of flag names to their values
values map[string]interface{}
}
// NewGenericFlagRegistrar creates a new GenericFlagRegistrar
func NewGenericFlagRegistrar() *GenericFlagRegistrar {
return &GenericFlagRegistrar{
values: make(map[string]interface{}),
}
}
// TransferFlags transfers all registered flags to the options
func (r *GenericFlagRegistrar) TransferFlags(cmd *cobra.Command, opts interface{}) {
if handler, ok := opts.(FlagHandler); ok {
flags := cmd.Flags()
// Transfer each registered flag
for name, value := range r.values {
changed := flags.Changed(name)
handler.HandleFlag(name, value, changed)
}
}
}
// RegisterBoolFlag registers a boolean flag and stores its reference
func (r *GenericFlagRegistrar) RegisterBoolFlag(cmd *cobra.Command, name string, value *bool, defaultValue bool, usage string) {
cmd.Flags().BoolVar(value, name, defaultValue, usage)
r.values[name] = value
}

View File

@ -1,140 +0,0 @@
package flags
import (
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
)
// MockFlagHandler implements FlagHandler for testing
type MockFlagHandler struct {
handledFlags map[string]interface{}
changedFlags map[string]bool
}
func NewMockFlagHandler() *MockFlagHandler {
return &MockFlagHandler{
handledFlags: make(map[string]interface{}),
changedFlags: make(map[string]bool),
}
}
func (h *MockFlagHandler) HandleFlag(name string, value interface{}, changed bool) {
h.handledFlags[name] = value
h.changedFlags[name] = changed
}
func TestNewGenericFlagRegistrar(t *testing.T) {
registrar := NewGenericFlagRegistrar()
assert.NotNil(t, registrar)
assert.NotNil(t, registrar.values)
assert.Len(t, registrar.values, 0)
}
func TestGenericFlagRegistrar_RegisterBoolFlag(t *testing.T) {
registrar := NewGenericFlagRegistrar()
cmd := &cobra.Command{Use: "test"}
var testFlag bool
registrar.RegisterBoolFlag(cmd, "test-flag", &testFlag, false, "Test flag")
// Verify the flag was registered
flag := cmd.Flags().Lookup("test-flag")
assert.NotNil(t, flag)
assert.Equal(t, "test-flag", flag.Name)
assert.Equal(t, "false", flag.DefValue)
assert.Equal(t, "Test flag", flag.Usage)
// Verify the value was stored in the registrar
value, exists := registrar.values["test-flag"]
assert.True(t, exists)
assert.Equal(t, &testFlag, value)
}
func TestGenericFlagRegistrar_TransferFlags_NoChanges(t *testing.T) {
registrar := NewGenericFlagRegistrar()
cmd := &cobra.Command{Use: "test"}
var testFlag bool
registrar.RegisterBoolFlag(cmd, "test-flag", &testFlag, false, "Test flag")
// Create a mock handler
handler := NewMockFlagHandler()
// Transfer flags (none changed)
registrar.TransferFlags(cmd, handler)
// Verify the handler was called with the right parameters
assert.Equal(t, &testFlag, handler.handledFlags["test-flag"])
assert.False(t, handler.changedFlags["test-flag"])
}
func TestGenericFlagRegistrar_TransferFlags_WithChanges(t *testing.T) {
registrar := NewGenericFlagRegistrar()
cmd := &cobra.Command{Use: "test"}
var testFlag bool
registrar.RegisterBoolFlag(cmd, "test-flag", &testFlag, false, "Test flag")
// Simulate flag being set on command line
err := cmd.Flags().Set("test-flag", "true")
assert.NoError(t, err)
testFlag = true // Value would be updated by cobra
// Create a mock handler
handler := NewMockFlagHandler()
// Transfer flags (with changes)
registrar.TransferFlags(cmd, handler)
// Verify the handler was called with the right parameters
assert.Equal(t, &testFlag, handler.handledFlags["test-flag"])
assert.True(t, handler.changedFlags["test-flag"])
assert.True(t, *handler.handledFlags["test-flag"].(*bool))
}
func TestGenericFlagRegistrar_TransferFlags_NonHandler(t *testing.T) {
registrar := NewGenericFlagRegistrar()
cmd := &cobra.Command{Use: "test"}
var testFlag bool
registrar.RegisterBoolFlag(cmd, "test-flag", &testFlag, false, "Test flag")
// Use a non-handler type
nonHandler := struct{}{}
// This should not panic
registrar.TransferFlags(cmd, nonHandler)
}
func TestGenericFlagRegistrar_MultipleFlags(t *testing.T) {
registrar := NewGenericFlagRegistrar()
cmd := &cobra.Command{Use: "test"}
var boolFlag bool
var boolFlag2 bool
registrar.RegisterBoolFlag(cmd, "bool-flag", &boolFlag, false, "Boolean flag")
registrar.RegisterBoolFlag(cmd, "bool-flag2", &boolFlag2, true, "Another boolean flag")
// Set one flag
err := cmd.Flags().Set("bool-flag", "true")
assert.NoError(t, err)
boolFlag = true // Value would be updated by cobra
// Create a mock handler
handler := NewMockFlagHandler()
// Transfer flags
registrar.TransferFlags(cmd, handler)
// Verify both flags were handled correctly
assert.Equal(t, &boolFlag, handler.handledFlags["bool-flag"])
assert.True(t, handler.changedFlags["bool-flag"])
assert.True(t, *handler.handledFlags["bool-flag"].(*bool))
assert.Equal(t, &boolFlag2, handler.handledFlags["bool-flag2"])
assert.False(t, handler.changedFlags["bool-flag2"])
assert.True(t, *handler.handledFlags["bool-flag2"].(*bool)) // Default is true
}

View File

@ -1,22 +0,0 @@
package flags
import "github.com/spf13/cobra"
// DiffFlagRegistrar handles flags specific to the diff command
type DiffFlagRegistrar struct {
*GenericFlagRegistrar
IncludeCRDs bool
}
// NewDiffFlagRegistrar creates a new DiffFlagRegistrar
func NewDiffFlagRegistrar() *DiffFlagRegistrar {
return &DiffFlagRegistrar{
GenericFlagRegistrar: NewGenericFlagRegistrar(),
}
}
// RegisterFlags registers diff-specific flags
func (r *DiffFlagRegistrar) RegisterFlags(cmd *cobra.Command) {
r.RegisterBoolFlag(cmd, "include-crds", &r.IncludeCRDs, false, "include CRDs in the diffing")
// Diff doesn't have skip-crds
}

View File

@ -0,0 +1,8 @@
package flags
// FlagHandler is a generic interface for handling flag values
type FlagHandler interface {
// HandleFlag receives a flag name, value, and whether it was changed
// Returns true if the flag was handled, false otherwise
HandleFlag(name string, value interface{}, changed bool) bool
}

View File

@ -0,0 +1,20 @@
package flags
// MockFlagHandler implements FlagHandler for testing
type MockFlagHandler struct {
handledFlags map[string]interface{}
changedFlags map[string]bool
}
func NewMockFlagHandler() *MockFlagHandler {
return &MockFlagHandler{
handledFlags: make(map[string]interface{}),
changedFlags: make(map[string]bool),
}
}
func (h *MockFlagHandler) HandleFlag(name string, value interface{}, changed bool) bool {
h.handledFlags[name] = value
h.changedFlags[name] = changed
return true
}

View File

@ -0,0 +1,51 @@
package flags
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestMockFlagHandler_HandleFlag_ReturnValue(t *testing.T) {
// Create a mock handler
mockHandler := NewMockFlagHandler()
// Test that it returns true for any flag
result := mockHandler.HandleFlag("test-flag", true, false)
assert.True(t, result, "MockHandler should return true for any flag")
// Test with multiple flags
result = mockHandler.HandleFlag("another-flag", "value", true)
assert.True(t, result, "MockHandler should return true for any flag")
// Verify the flags were stored correctly
assert.Equal(t, true, mockHandler.handledFlags["test-flag"])
assert.Equal(t, "value", mockHandler.handledFlags["another-flag"])
assert.Equal(t, false, mockHandler.changedFlags["test-flag"])
assert.Equal(t, true, mockHandler.changedFlags["another-flag"])
}
// CustomFlagHandler implements FlagHandler for testing specific return values
type CustomFlagHandler struct{}
func (h *CustomFlagHandler) HandleFlag(name string, value interface{}, changed bool) bool {
// Only handle specific flags
switch name {
case "known-flag":
return true
default:
return false
}
}
func TestCustomFlagHandler_HandleFlag_ReturnValue(t *testing.T) {
handler := &CustomFlagHandler{}
// Test with a recognized flag
result := handler.HandleFlag("known-flag", true, false)
assert.True(t, result, "Should return true for recognized flag")
// Test with an unrecognized flag
result = handler.HandleFlag("unknown-flag", true, false)
assert.False(t, result, "Should return false for unrecognized flag")
}

43
pkg/flags/flag_value.go Normal file
View File

@ -0,0 +1,43 @@
package flags
// GetFlagValue is a generic function to get flag values with type safety
func GetFlagValue[T any](registry FlagRegistry, name string) (T, bool) {
var zero T
values := registry.GetValues()
if value, exists := values[name]; exists {
if typedValue, ok := value.(*T); ok {
return *typedValue, true
}
}
return zero, false
}
// GetBoolFlagValue is a convenience function to get a boolean flag value
func GetBoolFlagValue(registry FlagRegistry, name string) (bool, bool) {
return GetFlagValue[bool](registry, name)
}
// GetStringFlagValue is a convenience function to get a string flag value
func GetStringFlagValue(registry FlagRegistry, name string) (string, bool) {
return GetFlagValue[string](registry, name)
}
// GetStringSliceFlagValue is a convenience function to get a string slice flag value
func GetStringSliceFlagValue(registry FlagRegistry, name string) ([]string, bool) {
return GetFlagValue[[]string](registry, name)
}
// GetIntFlagValue is a convenience function to get an integer flag value
func GetIntFlagValue(registry FlagRegistry, name string) (int, bool) {
return GetFlagValue[int](registry, name)
}
// GetInt64FlagValue is a convenience function to get an int64 flag value
func GetInt64FlagValue(registry FlagRegistry, name string) (int64, bool) {
return GetFlagValue[int64](registry, name)
}
// GetFloat64FlagValue is a convenience function to get a float64 flag value
func GetFloat64FlagValue(registry FlagRegistry, name string) (float64, bool) {
return GetFlagValue[float64](registry, name)
}

View File

@ -0,0 +1,220 @@
package flags
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetFlagValue(t *testing.T) {
registry := NewMockFlagRegistry()
// Register some test flags
boolValue := false
registry.values["bool-flag"] = &boolValue
stringValue := "test"
registry.values["string-flag"] = &stringValue
stringSliceValue := []string{"one", "two", "three"}
registry.values["string-slice-flag"] = &stringSliceValue
intValue := 42
registry.values["int-flag"] = &intValue
// Test getting boolean flag
gotBool, exists := GetFlagValue[bool](registry, "bool-flag")
assert.True(t, exists)
assert.Equal(t, false, gotBool)
// Test getting string flag
gotString, exists := GetFlagValue[string](registry, "string-flag")
assert.True(t, exists)
assert.Equal(t, "test", gotString)
// Test getting string slice flag
gotStringSlice, exists := GetFlagValue[[]string](registry, "string-slice-flag")
assert.True(t, exists)
assert.Equal(t, []string{"one", "two", "three"}, gotStringSlice)
// Test getting int flag
gotInt, exists := GetFlagValue[int](registry, "int-flag")
assert.True(t, exists)
assert.Equal(t, 42, gotInt)
// Test getting non-existent flag
_, exists = GetFlagValue[bool](registry, "non-existent")
assert.False(t, exists)
// Test getting flag with wrong type
_, exists = GetFlagValue[string](registry, "bool-flag")
assert.False(t, exists)
}
func TestGetBoolFlagValue(t *testing.T) {
registry := NewMockFlagRegistry()
// Register a boolean flag
boolValue := false
registry.values["bool-flag"] = &boolValue
// Test getting the flag value
value, exists := GetBoolFlagValue(registry, "bool-flag")
assert.True(t, exists)
assert.False(t, value)
// Change the value
*registry.values["bool-flag"].(*bool) = true
// Test getting the updated value
value, exists = GetBoolFlagValue(registry, "bool-flag")
assert.True(t, exists)
assert.True(t, value)
// Test getting a non-existent flag
value, exists = GetBoolFlagValue(registry, "non-existent")
assert.False(t, exists)
assert.False(t, value) // Default value for bool
// Test getting a flag with wrong type
stringValue := "test"
registry.values["string-flag"] = &stringValue
value, exists = GetBoolFlagValue(registry, "string-flag")
assert.False(t, exists)
assert.False(t, value) // Default value for bool
}
func TestGetStringFlagValue(t *testing.T) {
registry := NewMockFlagRegistry()
// Register a string flag
stringValue := "test"
registry.values["string-flag"] = &stringValue
// Test getting the flag value
value, exists := GetStringFlagValue(registry, "string-flag")
assert.True(t, exists)
assert.Equal(t, "test", value)
// Change the value
*registry.values["string-flag"].(*string) = "updated"
// Test getting the updated value
value, exists = GetStringFlagValue(registry, "string-flag")
assert.True(t, exists)
assert.Equal(t, "updated", value)
// Test getting a non-existent flag
value, exists = GetStringFlagValue(registry, "non-existent")
assert.False(t, exists)
assert.Equal(t, "", value) // Default value for string
// Test getting a flag with wrong type
boolValue := true
registry.values["bool-flag"] = &boolValue
value, exists = GetStringFlagValue(registry, "bool-flag")
assert.False(t, exists)
assert.Equal(t, "", value) // Default value for string
}
func TestGetStringSliceFlagValue(t *testing.T) {
registry := NewMockFlagRegistry()
// Register a string slice flag
sliceValue := []string{"one", "two", "three"}
registry.values["slice-flag"] = &sliceValue
// Test getting the flag value
value, exists := GetStringSliceFlagValue(registry, "slice-flag")
assert.True(t, exists)
assert.Equal(t, []string{"one", "two", "three"}, value)
// Change the value
*registry.values["slice-flag"].(*[]string) = []string{"updated"}
// Test getting the updated value
value, exists = GetStringSliceFlagValue(registry, "slice-flag")
assert.True(t, exists)
assert.Equal(t, []string{"updated"}, value)
// Test getting a non-existent flag
value, exists = GetStringSliceFlagValue(registry, "non-existent")
assert.False(t, exists)
assert.Nil(t, value) // Default value for slice
// Test getting a flag with wrong type
boolValue := true
registry.values["bool-flag"] = &boolValue
value, exists = GetStringSliceFlagValue(registry, "bool-flag")
assert.False(t, exists)
assert.Nil(t, value) // Default value for slice
}
func TestGetIntFlagValue(t *testing.T) {
registry := NewMockFlagRegistry()
// Register an int flag
intValue := 42
registry.values["int-flag"] = &intValue
// Test getting the flag value
value, exists := GetIntFlagValue(registry, "int-flag")
assert.True(t, exists)
assert.Equal(t, 42, value)
// Change the value
*registry.values["int-flag"].(*int) = 100
// Test getting the updated value
value, exists = GetIntFlagValue(registry, "int-flag")
assert.True(t, exists)
assert.Equal(t, 100, value)
// Test getting a non-existent flag
value, exists = GetIntFlagValue(registry, "non-existent")
assert.False(t, exists)
assert.Equal(t, 0, value) // Default value for int
// Test getting a flag with wrong type
boolValue := true
registry.values["bool-flag"] = &boolValue
value, exists = GetIntFlagValue(registry, "bool-flag")
assert.False(t, exists)
assert.Equal(t, 0, value) // Default value for int
}
func TestGetInt64FlagValue(t *testing.T) {
registry := NewMockFlagRegistry()
// Register an int64 flag
int64Value := int64(42)
registry.values["int64-flag"] = &int64Value
// Test getting the flag value
value, exists := GetInt64FlagValue(registry, "int64-flag")
assert.True(t, exists)
assert.Equal(t, int64(42), value)
// Test getting a non-existent flag
value, exists = GetInt64FlagValue(registry, "non-existent")
assert.False(t, exists)
assert.Equal(t, int64(0), value) // Default value for int64
}
func TestGetFloat64FlagValue(t *testing.T) {
registry := NewMockFlagRegistry()
// Register a float64 flag
float64Value := 3.14
registry.values["float64-flag"] = &float64Value
// Test getting the flag value
value, exists := GetFloat64FlagValue(registry, "float64-flag")
assert.True(t, exists)
assert.Equal(t, 3.14, value)
// Test getting a non-existent flag
value, exists = GetFloat64FlagValue(registry, "non-existent")
assert.False(t, exists)
assert.Equal(t, 0.0, value) // Default value for float64
}

64
pkg/flags/registry.go Normal file
View File

@ -0,0 +1,64 @@
package flags
import "github.com/spf13/cobra"
// FlagRegistry defines an interface for registering and transferring flags
type FlagRegistry interface {
RegisterFlags(cmd *cobra.Command)
TransferFlags(cmd *cobra.Command, opts interface{})
GetRegisteredFlagNames() []string
GetValues() map[string]interface{}
IsFlagRegistered(name string) bool
}
// GenericFlagRegistry is a base struct for flag registries
type GenericFlagRegistry struct {
// Map of flag names to their values
values map[string]interface{}
}
// NewGenericFlagRegistry creates a new GenericFlagRegistrar
func NewGenericFlagRegistry() *GenericFlagRegistry {
return &GenericFlagRegistry{
values: make(map[string]interface{}),
}
}
// GetValues returns the internal values map
func (r *GenericFlagRegistry) GetValues() map[string]interface{} {
return r.values
}
// GetRegisteredFlagNames returns the names of all registered flags
func (r *GenericFlagRegistry) GetRegisteredFlagNames() []string {
names := make([]string, 0, len(r.values))
for name := range r.values {
names = append(names, name)
}
return names
}
// IsFlagRegistered checks if a flag is registered in the registry
func (r *GenericFlagRegistry) IsFlagRegistered(name string) bool {
_, exists := r.values[name]
return exists
}
// TransferFlags transfers all registered flags to the options
func (r *GenericFlagRegistry) TransferFlags(cmd *cobra.Command, opts interface{}) {
if handler, ok := opts.(FlagHandler); ok {
flags := cmd.Flags()
// Transfer each registered flag
for name, value := range r.values {
changed := flags.Changed(name)
handler.HandleFlag(name, value, changed)
}
}
}
// RegisterBoolFlag registers a boolean flag and stores its reference
func (r *GenericFlagRegistry) RegisterBoolFlag(cmd *cobra.Command, name string, value *bool, defaultValue bool, usage string) {
cmd.Flags().BoolVar(value, name, defaultValue, usage)
r.values[name] = value
}

View File

@ -2,22 +2,22 @@ package flags
import "github.com/spf13/cobra"
// ApplyFlagRegistrar handles flags specific to the apply command
type ApplyFlagRegistrar struct {
*GenericFlagRegistrar
// ApplyFlagRegistry handles flags specific to the apply command
type ApplyFlagRegistry struct {
*GenericFlagRegistry
IncludeCRDs bool
SkipCRDs bool
}
// NewApplyFlagRegistrar creates a new ApplyFlagRegistrar
func NewApplyFlagRegistrar() *ApplyFlagRegistrar {
return &ApplyFlagRegistrar{
GenericFlagRegistrar: NewGenericFlagRegistrar(),
// NewApplyFlagRegistry creates a new ApplyFlagRegistry
func NewApplyFlagRegistry() *ApplyFlagRegistry {
return &ApplyFlagRegistry{
GenericFlagRegistry: NewGenericFlagRegistry(),
}
}
// RegisterFlags registers apply-specific flags
func (r *ApplyFlagRegistrar) RegisterFlags(cmd *cobra.Command) {
func (r *ApplyFlagRegistry) RegisterFlags(cmd *cobra.Command) {
r.RegisterBoolFlag(cmd, "include-crds", &r.IncludeCRDs, false, "include CRDs in the diffing")
r.RegisterBoolFlag(cmd, "skip-crds", &r.SkipCRDs, false, "if set, no CRDs will be installed on sync. By default, CRDs are installed if not already present")
}

View File

@ -0,0 +1,22 @@
package flags
import "github.com/spf13/cobra"
// DiffFlagRegistry handles flags specific to the diff command
type DiffFlagRegistry struct {
*GenericFlagRegistry
IncludeCRDs bool
}
// NewDiffFlagRegistry creates a new DiffFlagRegistry
func NewDiffFlagRegistry() *DiffFlagRegistry {
return &DiffFlagRegistry{
GenericFlagRegistry: NewGenericFlagRegistry(),
}
}
// RegisterFlags registers diff-specific flags
func (r *DiffFlagRegistry) RegisterFlags(cmd *cobra.Command) {
r.RegisterBoolFlag(cmd, "include-crds", &r.IncludeCRDs, false, "include CRDs in the diffing")
// Diff doesn't have skip-crds
}

View File

@ -0,0 +1,27 @@
package flags
import (
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
)
func TestDiffFlagRegisty(t *testing.T) {
registry := NewDiffFlagRegistry()
// Create a test command to register flags
cmd := &cobra.Command{Use: "test"}
registry.RegisterFlags(cmd)
// Get the names of registered flags
registeredFlags := registry.GetRegisteredFlagNames()
// Verify that include-crds and skip-crds are registered
assert.Contains(t, registeredFlags, "include-crds")
// Get and verify the default values using the generic function
includeCRDs, exists := GetFlagValue[bool](registry, "include-crds")
assert.True(t, exists)
assert.False(t, includeCRDs)
}

View File

@ -0,0 +1,24 @@
package flags
import "github.com/spf13/cobra"
// MockFlagRegistry implements FlagRegistrar for testing
type MockFlagRegistry struct {
*GenericFlagRegistry
}
func NewMockFlagRegistry() *MockFlagRegistry {
return &MockFlagRegistry{
GenericFlagRegistry: NewGenericFlagRegistry(),
}
}
// RegisterFlags implements the FlagRegistrar interface for testing
func (r *MockFlagRegistry) RegisterFlags(cmd *cobra.Command) {
// Mock implementation does nothing
}
// GetValues returns the internal values map
func (r *MockFlagRegistry) GetValues() map[string]interface{} {
return r.values
}

View File

@ -2,22 +2,22 @@ package flags
import "github.com/spf13/cobra"
// SyncFlagRegistrar handles flags specific to the sync command
type SyncFlagRegistrar struct {
*GenericFlagRegistrar
// SyncFlagRegistry handles flags specific to the sync command
type SyncFlagRegistry struct {
*GenericFlagRegistry
IncludeCRDs bool
SkipCRDs bool
}
// NewSyncFlagRegistrar creates a new SyncFlagRegistrar
func NewSyncFlagRegistrar() *SyncFlagRegistrar {
return &SyncFlagRegistrar{
GenericFlagRegistrar: NewGenericFlagRegistrar(),
// NewSyncFlagRegistry creates a new SyncFlagRegistry
func NewSyncFlagRegistry() *SyncFlagRegistry {
return &SyncFlagRegistry{
GenericFlagRegistry: NewGenericFlagRegistry(),
}
}
// RegisterFlags registers sync-specific flags
func (r *SyncFlagRegistrar) RegisterFlags(cmd *cobra.Command) {
func (r *SyncFlagRegistry) RegisterFlags(cmd *cobra.Command) {
r.RegisterBoolFlag(cmd, "include-crds", &r.IncludeCRDs, false, "include CRDs in the diffing")
r.RegisterBoolFlag(cmd, "skip-crds", &r.SkipCRDs, false, "if set, no CRDs will be installed on sync. By default, CRDs are installed if not already present")
}

View File

@ -0,0 +1,22 @@
package flags
import "github.com/spf13/cobra"
// TemplateFlagRegistry handles flags specific to the template command
type TemplateFlagRegistry struct {
*GenericFlagRegistry
IncludeCRDs bool
}
// NewTemplateFlagRegistry creates a new TemplateFlagRegistry
func NewTemplateFlagRegistry() *TemplateFlagRegistry {
return &TemplateFlagRegistry{
GenericFlagRegistry: NewGenericFlagRegistry(),
}
}
// RegisterFlags registers template-specific flags
func (r *TemplateFlagRegistry) RegisterFlags(cmd *cobra.Command) {
r.RegisterBoolFlag(cmd, "include-crds", &r.IncludeCRDs, false, "include CRDs in the diffing")
// Template doesn't have skip-crds
}

176
pkg/flags/registry_test.go Normal file
View File

@ -0,0 +1,176 @@
package flags
import (
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
)
func TestNewGenericFlagRegistrar(t *testing.T) {
registry := NewMockFlagRegistry()
assert.NotNil(t, registry)
assert.NotNil(t, registry.values)
assert.Len(t, registry.values, 0)
}
func TestGenericFlagRegistry_RegisterBoolFlag(t *testing.T) {
registry := NewMockFlagRegistry()
cmd := &cobra.Command{Use: "test"}
var testFlag bool
registry.RegisterBoolFlag(cmd, "test-flag", &testFlag, false, "Test flag")
// Verify the flag was registered
flag := cmd.Flags().Lookup("test-flag")
assert.NotNil(t, flag)
assert.Equal(t, "test-flag", flag.Name)
assert.Equal(t, "false", flag.DefValue)
assert.Equal(t, "Test flag", flag.Usage)
// Verify the value was stored in the registry
value, exists := registry.values["test-flag"]
assert.True(t, exists)
assert.Equal(t, &testFlag, value)
// Test the generic GetFlagValue function
flagValue, exists := GetFlagValue[bool](registry, "test-flag")
assert.True(t, exists)
assert.Equal(t, false, flagValue)
}
func TestGenericFlagRegistry_TransferFlags_NoChanges(t *testing.T) {
registry := NewMockFlagRegistry()
cmd := &cobra.Command{Use: "test"}
var testFlag bool
registry.RegisterBoolFlag(cmd, "test-flag", &testFlag, false, "Test flag")
// Create a mock handler
handler := NewMockFlagHandler()
// Transfer flags (none changed)
registry.TransferFlags(cmd, handler)
// Verify the handler was called with the right parameters
assert.Equal(t, &testFlag, handler.handledFlags["test-flag"])
assert.False(t, handler.changedFlags["test-flag"])
}
func TestGenericFlagRegistry_TransferFlags_WithChanges(t *testing.T) {
registry := NewMockFlagRegistry()
cmd := &cobra.Command{Use: "test"}
var testFlag bool
registry.RegisterBoolFlag(cmd, "test-flag", &testFlag, false, "Test flag")
// Simulate flag being set on command line
err := cmd.Flags().Set("test-flag", "true")
assert.NoError(t, err)
testFlag = true // Value would be updated by cobra
// Create a mock handler
handler := NewMockFlagHandler()
// Transfer flags (with changes)
registry.TransferFlags(cmd, handler)
// Verify the handler was called with the right parameters
assert.Equal(t, &testFlag, handler.handledFlags["test-flag"])
assert.True(t, handler.changedFlags["test-flag"])
assert.True(t, *handler.handledFlags["test-flag"].(*bool))
}
func TestGenericFlagRegistry_TransferFlags_NonHandler(t *testing.T) {
registry := NewMockFlagRegistry()
cmd := &cobra.Command{Use: "test"}
var testFlag bool
registry.RegisterBoolFlag(cmd, "test-flag", &testFlag, false, "Test flag")
// Use a non-handler type
nonHandler := struct{}{}
// This should not panic
registry.TransferFlags(cmd, nonHandler)
}
func TestGenericFlagRegistry_MultipleFlags(t *testing.T) {
registry := NewMockFlagRegistry()
cmd := &cobra.Command{Use: "test"}
var boolFlag bool
var boolFlag2 bool
registry.RegisterBoolFlag(cmd, "bool-flag", &boolFlag, false, "Boolean flag")
registry.RegisterBoolFlag(cmd, "bool-flag2", &boolFlag2, true, "Another boolean flag")
// Set one flag
err := cmd.Flags().Set("bool-flag", "true")
assert.NoError(t, err)
boolFlag = true // Value would be updated by cobra
// Create a mock handler
handler := NewMockFlagHandler()
// Transfer flags
registry.TransferFlags(cmd, handler)
// Verify both flags were handled correctly
assert.Equal(t, &boolFlag, handler.handledFlags["bool-flag"])
assert.True(t, handler.changedFlags["bool-flag"])
assert.True(t, *handler.handledFlags["bool-flag"].(*bool))
assert.Equal(t, &boolFlag2, handler.handledFlags["bool-flag2"])
assert.False(t, handler.changedFlags["bool-flag2"])
assert.True(t, *handler.handledFlags["bool-flag2"].(*bool)) // Default is true
}
func TestGenericFlagRegistry_GetRegisteredFlagNames(t *testing.T) {
registry := NewMockFlagRegistry()
// Register some test flags
boolValue := false
registry.values["bool-flag"] = &boolValue
stringValue := "test"
registry.values["string-flag"] = &stringValue
stringSliceValue := []string{"one", "two", "three"}
registry.values["string-slice-flag"] = &stringSliceValue
// Test GetRegisteredFlagNames
names := registry.GetRegisteredFlagNames()
assert.Len(t, names, 3)
assert.Contains(t, names, "bool-flag")
assert.Contains(t, names, "string-flag")
assert.Contains(t, names, "string-slice-flag")
}
func TestGenericFlagRegistry_IsFlagRegistered(t *testing.T) {
registry := NewGenericFlagRegistry()
// Initially, no flags are registered
assert.False(t, registry.IsFlagRegistered("test-flag"))
// Register a flag
boolValue := false
registry.values["test-flag"] = &boolValue
// Now the flag should be registered
assert.True(t, registry.IsFlagRegistered("test-flag"))
// Check a non-existent flag
assert.False(t, registry.IsFlagRegistered("non-existent"))
// Register another flag
stringValue := "test"
registry.values["string-flag"] = &stringValue
// Both flags should be registered
assert.True(t, registry.IsFlagRegistered("test-flag"))
assert.True(t, registry.IsFlagRegistered("string-flag"))
// Case sensitivity check
assert.False(t, registry.IsFlagRegistered("TEST-FLAG"))
}

View File

@ -1,21 +0,0 @@
package flags
import "github.com/spf13/cobra"
// TemplateFlagRegistrar handles flags specific to the template command
type TemplateFlagRegistrar struct {
*GenericFlagRegistrar
IncludeCRDs bool
}
// NewTemplateFlagRegistrar creates a new TemplateFlagRegistrar
func NewTemplateFlagRegistrar() *TemplateFlagRegistrar {
return &TemplateFlagRegistrar{
GenericFlagRegistrar: NewGenericFlagRegistrar(),
}
}
// RegisterFlags registers template-specific flags
func (r *TemplateFlagRegistrar) RegisterFlags(cmd *cobra.Command) {
r.RegisterBoolFlag(cmd, "include-crds", &r.IncludeCRDs, false, "include CRDs in the diffing")
}