mirror of
https://github.com/open-cluster-management-io/ocm.git
synced 2026-08-23 22:26:49 +00:00
* 🐛 Apply TLS profile to spoke agent serving endpoints The spoke agent binaries (klusterlet-agent, registration-agent, work-agent) receive --tls-min-version and --tls-cipher-suites flags from the klusterlet operator via deployment template rendering, but do not wire them to their own library-go GenericAPIServer serving endpoint on port 8443. Add ApplyTLSToCommand to all three spoke agent command constructors (NewKlusterletAgentCmd, NewRegistrationAgent, NewWorkAgent) so the TLS flags are consumed by the PersistentPreRunE hook and applied to the serving config. This matches the pattern already used by all hub component commands (registration-controller, placement, work, addon-manager, grpc-server). Signed-off-by: Tesshu Flower <tflower@redhat.com> * 🐛 Apply TLS profile to operator serving endpoints via ConfigMap The cluster-manager operator (hub) and klusterlet operator (spoke) serve a health/metrics endpoint on port 8443 via library-go's GenericAPIServer. Unlike the hub controllers and spoke agents they manage, these operator binaries do not receive --tls-min-version and --tls-cipher-suites flags from their deployment manifests -- no external component injects those flags into the operator pods. Instead, the operators have direct access to the ocm-tls-profile ConfigMap in their namespace at startup: - The cluster-manager operator reads it in RunClusterManagerOperator via StartTLSConfigMapWatcher and calls os.Exit(0) on changes to restart. - The klusterlet operator has a tls-profile-sync sidecar that writes the ConfigMap from the local OCP APIServer TLS profile and triggers pod restarts. Add ApplyTLSFromConfigMapToCommand to both operator commands. This installs a PersistentPreRunE hook that reads the ocm-tls-profile ConfigMap once (using an in-cluster kube client) before library-go's StartController creates the server, writes a minimal GenericOperatorConfig YAML to /tmp, and sets --config to point at it. This ensures the server's TLS config is set correctly from the first request, using the same restart-on-change mechanism already in place. The ConfigMap is optional: if not found (upstream deployments without the ACM sidecar) or if the in-cluster config is unavailable (local dev), the hook is a no-op and library-go defaults apply. Also add ApplyTLSToCommand call-site comments to the three spoke agent commands explaining the difference: agents receive TLS flags from their deployment manifests (injected by the klusterlet operator), while the operator itself reads the ConfigMap directly. Signed-off-by: Tesshu Flower <tflower@redhat.com> --------- Signed-off-by: Tesshu Flower <tflower@redhat.com>
197 lines
5.3 KiB
Go
197 lines
5.3 KiB
Go
package spoke
|
|
|
|
import (
|
|
"io"
|
|
"testing"
|
|
|
|
"k8s.io/component-base/featuregate"
|
|
|
|
"open-cluster-management.io/ocm/pkg/features"
|
|
)
|
|
|
|
func TestAgentCmdName(t *testing.T) {
|
|
// Test the constant
|
|
if agentCmdName != "agent" {
|
|
t.Errorf("Expected agentCmdName to be 'agent', got %q", agentCmdName)
|
|
}
|
|
}
|
|
|
|
func TestNewKlusterletOperatorCmd(t *testing.T) {
|
|
cmd := NewKlusterletOperatorCmd()
|
|
|
|
if cmd == nil {
|
|
t.Fatal("NewKlusterletOperatorCmd() returned nil")
|
|
}
|
|
|
|
// Test command properties
|
|
if cmd.Use != "klusterlet" {
|
|
t.Errorf("Expected Use to be 'klusterlet', got %q", cmd.Use)
|
|
}
|
|
|
|
if cmd.Short != "Start the klusterlet operator" {
|
|
t.Errorf("Expected Short to be 'Start the klusterlet operator', got %q", cmd.Short)
|
|
}
|
|
|
|
// Test that flags are added
|
|
flags := cmd.Flags()
|
|
if flags == nil {
|
|
t.Error("Expected flags to be set")
|
|
}
|
|
|
|
// Verify command is runnable (has RunE or Run set)
|
|
if cmd.RunE == nil && cmd.Run == nil {
|
|
t.Error("Expected command to have RunE or Run set")
|
|
}
|
|
}
|
|
|
|
func TestKlusterletOperatorFlags(t *testing.T) {
|
|
cmd := NewKlusterletOperatorCmd()
|
|
flags := cmd.Flags()
|
|
|
|
// Test that specific flags are present
|
|
expectedFlags := map[string]string{
|
|
"skip-placeholder-hub-secret": "bool",
|
|
"control-plane-node-label-selector": "string",
|
|
"deployment-replicas": "int32",
|
|
"disable-default-addon-namespace": "bool",
|
|
"enable-sync-labels": "bool",
|
|
}
|
|
|
|
for flagName, flagType := range expectedFlags {
|
|
flag := flags.Lookup(flagName)
|
|
if flag == nil {
|
|
t.Errorf("Expected flag %q to be present", flagName)
|
|
continue
|
|
}
|
|
|
|
switch flagType {
|
|
case "bool":
|
|
if flag.Value.Type() != "bool" {
|
|
t.Errorf("Expected flag %q to be bool type, got %q", flagName, flag.Value.Type())
|
|
}
|
|
case "string":
|
|
if flag.Value.Type() != "string" {
|
|
t.Errorf("Expected flag %q to be string type, got %q", flagName, flag.Value.Type())
|
|
}
|
|
case "int32":
|
|
if flag.Value.Type() != "int32" {
|
|
t.Errorf("Expected flag %q to be int32 type, got %q", flagName, flag.Value.Type())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestKlusterletOperatorTLSFromConfigMapWiring(t *testing.T) {
|
|
cmd := NewKlusterletOperatorCmd()
|
|
|
|
// ApplyTLSFromConfigMapToCommand should have set PersistentPreRunE.
|
|
if cmd.PersistentPreRunE == nil {
|
|
t.Error("Expected PersistentPreRunE to be set by ApplyTLSFromConfigMapToCommand")
|
|
}
|
|
}
|
|
|
|
func TestKlusterletOperatorDeprecatedFlag(t *testing.T) {
|
|
cmd := NewKlusterletOperatorCmd()
|
|
flags := cmd.Flags()
|
|
|
|
// Test that deprecated flag exists and is marked as deprecated
|
|
flag := flags.Lookup("skip-placeholder-hub-secret")
|
|
if flag == nil {
|
|
t.Error("Expected deprecated flag 'skip-placeholder-hub-secret' to be present")
|
|
return
|
|
}
|
|
|
|
// In cobra, deprecated flags are still present but marked
|
|
if flag.Deprecated == "" {
|
|
t.Error("Expected 'skip-placeholder-hub-secret' flag to be marked as deprecated")
|
|
}
|
|
}
|
|
|
|
func TestKlusterletAgentTLSWiring(t *testing.T) {
|
|
// Reset feature gate for this test
|
|
old := features.SpokeMutableFeatureGate
|
|
features.SpokeMutableFeatureGate = featuregate.NewFeatureGate()
|
|
t.Cleanup(func() { features.SpokeMutableFeatureGate = old })
|
|
|
|
cmd := NewKlusterletAgentCmd()
|
|
|
|
// ApplyTLSToCommand should have set PersistentPreRunE
|
|
if cmd.PersistentPreRunE == nil {
|
|
t.Error("Expected PersistentPreRunE to be set by ApplyTLSToCommand")
|
|
}
|
|
|
|
// TLS flags should be registered via common options
|
|
flags := cmd.Flags()
|
|
if flags.Lookup("tls-min-version") == nil {
|
|
t.Error("Expected --tls-min-version flag to be registered")
|
|
}
|
|
if flags.Lookup("tls-cipher-suites") == nil {
|
|
t.Error("Expected --tls-cipher-suites flag to be registered")
|
|
}
|
|
}
|
|
|
|
func TestNewKlusterletAgentCmd(t *testing.T) {
|
|
// Reset feature gate for this test
|
|
old := features.SpokeMutableFeatureGate
|
|
features.SpokeMutableFeatureGate = featuregate.NewFeatureGate()
|
|
t.Cleanup(func() { features.SpokeMutableFeatureGate = old })
|
|
|
|
cmd := NewKlusterletAgentCmd()
|
|
|
|
if cmd == nil {
|
|
t.Fatal("NewKlusterletAgentCmd() returned nil")
|
|
}
|
|
|
|
// Test command properties
|
|
if cmd.Use != agentCmdName {
|
|
t.Errorf("Expected Use to be %q, got %q", agentCmdName, cmd.Use)
|
|
}
|
|
|
|
if cmd.Short != "Start the klusterlet agent" {
|
|
t.Errorf("Expected Short to be 'Start the klusterlet agent', got %q", cmd.Short)
|
|
}
|
|
|
|
// Test that flags are added
|
|
flags := cmd.Flags()
|
|
if flags == nil {
|
|
t.Error("Expected flags to be set")
|
|
}
|
|
|
|
// Verify command is runnable (has RunE or Run set)
|
|
if cmd.RunE == nil && cmd.Run == nil {
|
|
t.Error("Expected command to have RunE or Run set")
|
|
}
|
|
}
|
|
|
|
func TestKlusterletAgentFlags(t *testing.T) {
|
|
// Reset feature gate for this test
|
|
old := features.SpokeMutableFeatureGate
|
|
features.SpokeMutableFeatureGate = featuregate.NewFeatureGate()
|
|
t.Cleanup(func() { features.SpokeMutableFeatureGate = old })
|
|
|
|
cmd := NewKlusterletAgentCmd()
|
|
flags := cmd.Flags()
|
|
|
|
// Test that essential flags are present
|
|
if !flags.HasFlags() {
|
|
t.Error("Expected command to have flags")
|
|
}
|
|
|
|
// Check for feature gate flag
|
|
featureGateFlag := flags.Lookup("feature-gates")
|
|
if featureGateFlag == nil {
|
|
t.Error("Expected feature-gates flag to be present")
|
|
}
|
|
|
|
// Test flag parsing with help
|
|
cmd.SetArgs([]string{"--help"})
|
|
cmd.SilenceUsage = true
|
|
cmd.SilenceErrors = true
|
|
cmd.SetOut(io.Discard)
|
|
cmd.SetErr(io.Discard)
|
|
err := cmd.Execute()
|
|
if err != nil {
|
|
t.Errorf("Flag parsing with --help failed: %v", err)
|
|
}
|
|
}
|