diff --git a/operator/pkg/tasks/init/test_helpers.go b/operator/pkg/tasks/init/test_helpers.go new file mode 100644 index 000000000000..5dbda5f03529 --- /dev/null +++ b/operator/pkg/tasks/init/test_helpers.go @@ -0,0 +1,155 @@ +/* +Copyright 2024 The Karmada Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package tasks + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + + operatorv1alpha1 "github.com/karmada-io/karmada/operator/pkg/apis/operator/v1alpha1" + "github.com/karmada-io/karmada/operator/pkg/certs" +) + +// TestInterface defines the interface for retrieving test data. +type TestInterface interface { + // Get returns the data from the test instance. + Get() string +} + +// MyTestData is a struct that implements the TestInterface. +type MyTestData struct { + Data string +} + +// Get returns the data stored in the MyTestData struct. +func (m *MyTestData) Get() string { + return m.Data +} + +// TestInitData contains the configuration and state required to initialize Karmada components. +type TestInitData struct { + name string + namespace string + controlplaneConfig *rest.Config + dataDir string + crdTarball operatorv1alpha1.CRDTarball + karmadaVersion string + components *operatorv1alpha1.KarmadaComponents + featureGates map[string]bool + remoteClient kubernetes.Interface + karmadaClient kubernetes.Interface + controlplaneAddr string + certs []*certs.KarmadaCert +} + +// Ensure TestInitData implements InitData interface at compile time. +var _ InitData = &TestInitData{} + +// GetName returns the name of the current Karmada installation. +func (t *TestInitData) GetName() string { + return t.name +} + +// GetNamespace returns the namespace of the current Karmada installation. +func (t *TestInitData) GetNamespace() string { + return t.namespace +} + +// SetControlplaneConfig sets the control plane configuration for Karmada. +func (t *TestInitData) SetControlplaneConfig(config *rest.Config) { + t.controlplaneConfig = config +} + +// ControlplaneConfig returns the control plane configuration. +func (t *TestInitData) ControlplaneConfig() *rest.Config { + return t.controlplaneConfig +} + +// ControlplaneAddress returns the address of the control plane. +func (t *TestInitData) ControlplaneAddress() string { + return t.controlplaneAddr +} + +// RemoteClient returns the Kubernetes client for remote interactions. +func (t *TestInitData) RemoteClient() kubernetes.Interface { + return t.remoteClient +} + +// KarmadaClient returns the Kubernetes client for interacting with Karmada. +func (t *TestInitData) KarmadaClient() kubernetes.Interface { + return t.karmadaClient +} + +// DataDir returns the data directory used by Karmada. +func (t *TestInitData) DataDir() string { + return t.dataDir +} + +// CrdTarball returns the CRD tarball used for Karmada installation. +func (t *TestInitData) CrdTarball() operatorv1alpha1.CRDTarball { + return t.crdTarball +} + +// KarmadaVersion returns the version of Karmada being used. +func (t *TestInitData) KarmadaVersion() string { + return t.karmadaVersion +} + +// Components returns the Karmada components used in the current installation. +func (t *TestInitData) Components() *operatorv1alpha1.KarmadaComponents { + return t.components +} + +// FeatureGates returns the feature gates enabled for the current installation. +func (t *TestInitData) FeatureGates() map[string]bool { + return t.featureGates +} + +// AddCert adds a Karmada certificate to the TestInitData. +func (t *TestInitData) AddCert(cert *certs.KarmadaCert) { + t.certs = append(t.certs, cert) +} + +// GetCert retrieves a Karmada certificate by its name. +func (t *TestInitData) GetCert(name string) *certs.KarmadaCert { + for _, cert := range t.certs { + if cert.CertName() == name { + return cert + } + } + return nil +} + +// CertList returns a list of all Karmada certificates stored in TestInitData. +func (t *TestInitData) CertList() []*certs.KarmadaCert { + return t.certs +} + +// LoadCertFromSecret loads a Karmada certificate from a Kubernetes secret. +func (t *TestInitData) LoadCertFromSecret(secret *corev1.Secret) error { + if len(secret.Data) == 0 { + return fmt.Errorf("cert data is empty") + } + + // Dummy implementation: load empty certificate. + cert := &certs.KarmadaCert{} + t.AddCert(cert) + return nil +} diff --git a/operator/pkg/util/util.go b/operator/pkg/util/util.go index 6214527211c5..1a276ca63db2 100644 --- a/operator/pkg/util/util.go +++ b/operator/pkg/util/util.go @@ -25,14 +25,32 @@ import ( "net/http" "os" "path/filepath" + "reflect" "regexp" "strings" "time" + clientset "k8s.io/client-go/kubernetes" "k8s.io/klog/v2" "sigs.k8s.io/yaml" + operatorv1alpha1 "github.com/karmada-io/karmada/operator/pkg/apis/operator/v1alpha1" + "github.com/karmada-io/karmada/operator/pkg/workflow" "github.com/karmada-io/karmada/pkg/util" + + "k8s.io/client-go/rest" +) + +var ( + // ClientFactory creates a new Kubernetes clientset from the provided kubeconfig. + ClientFactory = func(kubeconfig *rest.Config) (clientset.Interface, error) { + return clientset.NewForConfig(kubeconfig) + } + + // BuildClientFromSecretRefFactory constructs a Kubernetes clientset using a LocalSecretReference. + BuildClientFromSecretRefFactory = func(client clientset.Interface, ref *operatorv1alpha1.LocalSecretReference) (clientset.Interface, error) { + return BuildClientFromSecretRef(client, ref) + } ) // Downloader Download progress @@ -222,3 +240,53 @@ func ReplaceYamlForReg(path, destResource string, reg *regexp.Regexp) ([]byte, e repl := reg.ReplaceAllString(string(data), destResource) return yaml.YAMLToJSON([]byte(repl)) } + +// ContainAllTasks checks if all tasks in the subset are present in the tasks slice. +// Returns an error if any subset task is not found; nil otherwise. +func ContainAllTasks(tasks, subset []workflow.Task) error { + for _, subsetTask := range subset { + found := false + for _, task := range tasks { + found = DeepEqualTasks(task, subsetTask) == nil + if found { + break + } + } + if !found { + return fmt.Errorf("subset task %v not found in tasks", subsetTask) + } + } + return nil +} + +// DeepEqualTasks compares two workflow.Task instances for deep equality. +// Returns an error if they are not equal; nil if they are equal. +func DeepEqualTasks(t1, t2 workflow.Task) error { + if t1.Name != t2.Name { + return fmt.Errorf("expected t1 name %s, but got %s", t2.Name, t1.Name) + } + + if t1.RunSubTasks != t2.RunSubTasks { + return fmt.Errorf("expected t1 RunSubTasks flag %t, but got %t", t2.RunSubTasks, t1.RunSubTasks) + } + + if len(t1.Tasks) != len(t2.Tasks) { + return fmt.Errorf("expected t1 tasks length %d, but got %d", len(t2.Tasks), len(t1.Tasks)) + } + + for index := range t1.Tasks { + err := DeepEqualTasks(t1.Tasks[index], t2.Tasks[index]) + if err != nil { + return fmt.Errorf("unexpected error; tasks are not equal, got %v", err) + } + } + + if reflect.ValueOf(t1.Skip).Pointer() != reflect.ValueOf(t2.Skip).Pointer() { + return fmt.Errorf("expected t1 Skip func %v, but got %v", reflect.ValueOf(t2.Skip).Pointer(), reflect.ValueOf(t1.Skip).Pointer()) + } + if reflect.ValueOf(t1.Run).Pointer() != reflect.ValueOf(t2.Run).Pointer() { + return fmt.Errorf("expected t1 Run func %v, but got %v", reflect.ValueOf(t2.Run).Pointer(), reflect.ValueOf(t1.Run).Pointer()) + } + + return nil +}